Revert "Revert "external/boringssl: Sync to 81080a729af568f7b5fde92b9170cc17065027c9.""

This reverts commit 228bd6249d17f351ea66508b3ec3112ed1cbdf30.

Reason for revert: All fixes submitted for modules affected by the ENGINE_free API change.

Change-Id: I30fafafa13ec0a6390f4a9211fbf3122a8b4865f
diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt
index 5cdfa40..477faae 100644
--- a/src/crypto/CMakeLists.txt
+++ b/src/crypto/CMakeLists.txt
@@ -87,7 +87,7 @@
 add_subdirectory(fipsmodule)
 add_subdirectory(test)
 
-if(FIPS_DELOCATE)
+if(FIPS_DELOCATE OR FIPS_SHARED)
   SET_SOURCE_FILES_PROPERTIES(fipsmodule/bcm.o PROPERTIES EXTERNAL_OBJECT true)
   SET_SOURCE_FILES_PROPERTIES(fipsmodule/bcm.o PROPERTIES GENERATED true)
 
@@ -115,6 +115,7 @@
 
     chacha/chacha-armv8.${ASM_EXT}
     test/trampoline-armv8.${ASM_EXT}
+    third_party/sike/asm/fp-armv8.${ASM_EXT}
   )
 endif()
 
@@ -136,6 +137,7 @@
     cipher_extra/chacha20_poly1305_x86_64.${ASM_EXT}
     hrss/asm/poly_rq_mul.S
     test/trampoline-x86_64.${ASM_EXT}
+    third_party/sike/asm/fp-x86_64.${ASM_EXT}
   )
 endif()
 
@@ -145,6 +147,8 @@
 perlasm(chacha/chacha-x86_64.${ASM_EXT} chacha/asm/chacha-x86_64.pl)
 perlasm(cipher_extra/aes128gcmsiv-x86_64.${ASM_EXT} cipher_extra/asm/aes128gcmsiv-x86_64.pl)
 perlasm(cipher_extra/chacha20_poly1305_x86_64.${ASM_EXT} cipher_extra/asm/chacha20_poly1305_x86_64.pl)
+perlasm(third_party/sike/asm/fp-x86_64.${ASM_EXT} ../third_party/sike/asm/fp-x86_64.pl)
+perlasm(third_party/sike/asm/fp-armv8.${ASM_EXT} ../third_party/sike/asm/fp-armv8.pl)
 perlasm(test/trampoline-armv4.${ASM_EXT} test/asm/trampoline-armv4.pl)
 perlasm(test/trampoline-armv8.${ASM_EXT} test/asm/trampoline-armv8.pl)
 perlasm(test/trampoline-x86.${ASM_EXT} test/asm/trampoline-x86.pl)
@@ -278,6 +282,8 @@
   evp/p_ed25519_asn1.c
   evp/p_rsa.c
   evp/p_rsa_asn1.c
+  evp/p_x25519.c
+  evp/p_x25519_asn1.c
   evp/pbkdf.c
   evp/print.c
   evp/scrypt.c
@@ -317,6 +323,7 @@
   rsa_extra/rsa_asn1.c
   rsa_extra/rsa_print.c
   stack/stack.c
+  siphash/siphash.c
   thread.c
   thread_none.c
   thread_pthread.c
@@ -404,6 +411,11 @@
   x509v3/v3_sxnet.c
   x509v3/v3_utl.c
   ../third_party/fiat/curve25519.c
+  ../third_party/sike/fpx.c
+  ../third_party/sike/isogeny.c
+  ../third_party/sike/curve_params.c
+  ../third_party/sike/sike.c
+  ../third_party/sike/asm/fp_generic.c
 
   $<TARGET_OBJECTS:fipsmodule>
 
@@ -411,9 +423,25 @@
   ${CRYPTO_FIPS_OBJECTS}
 )
 
+if(FIPS_SHARED)
+  # Rewrite libcrypto.so to inject the correct module hash value. This assumes
+  # UNIX-style library naming, but we only support FIPS mode on Linux anyway.
+  add_custom_command(
+    TARGET crypto POST_BUILD
+    COMMAND ${GO_EXECUTABLE} run
+    ${CMAKE_CURRENT_SOURCE_DIR}/../util/fipstools/inject_hash/inject_hash.go
+    -o libcrypto.so -in-object libcrypto.so
+    # The DEPENDS argument to a POST_BUILD rule appears to be ignored. Thus
+    # go_executable isn't used (as it doesn't get built), but we list this
+    # dependency anyway in case it starts working in some CMake version.
+    DEPENDS ../util/fipstools/inject_hash/inject_hash.go
+    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+  )
+endif()
+
 add_dependencies(crypto global_target)
 
-if(FIPS_DELOCATE)
+if(FIPS_DELOCATE OR FIPS_SHARED)
   add_dependencies(crypto bcm_o_target)
 endif()
 
@@ -483,12 +511,14 @@
   rsa_extra/rsa_test.cc
   self_test.cc
   stack/stack_test.cc
+  siphash/siphash_test.cc
   test/file_test_gtest.cc
   thread_test.cc
   x509/x509_test.cc
   x509/x509_time_test.cc
   x509v3/tab_test.cc
   x509v3/v3name_test.cc
+  ../third_party/sike/sike_test.cc
 
   $<TARGET_OBJECTS:crypto_test_data>
   $<TARGET_OBJECTS:boringssl_gtest_main>
diff --git a/src/crypto/base64/base64_test.cc b/src/crypto/base64/base64_test.cc
index 9122dee..6905659 100644
--- a/src/crypto/base64/base64_test.cc
+++ b/src/crypto/base64/base64_test.cc
@@ -39,14 +39,14 @@
   invalid,
 };
 
-struct TestVector {
+struct Base64TestVector {
   enum encoding_relation relation;
   const char *decoded;
   const char *encoded;
 };
 
 // Test vectors from RFC 4648.
-static const TestVector kTestVectors[] = {
+static const Base64TestVector kTestVectors[] = {
     {canonical, "", ""},
     {canonical, "f", "Zg==\n"},
     {canonical, "fo", "Zm8=\n"},
@@ -103,7 +103,7 @@
      "=======\n"},
 };
 
-class Base64Test : public testing::TestWithParam<TestVector> {};
+class Base64Test : public testing::TestWithParam<Base64TestVector> {};
 
 INSTANTIATE_TEST_SUITE_P(, Base64Test, testing::ValuesIn(kTestVectors));
 
@@ -122,7 +122,7 @@
 }
 
 TEST_P(Base64Test, EncodeBlock) {
-  const TestVector &t = GetParam();
+  const Base64TestVector &t = GetParam();
   if (t.relation != canonical) {
     return;
   }
@@ -140,7 +140,7 @@
 }
 
 TEST_P(Base64Test, DecodeBase64) {
-  const TestVector &t = GetParam();
+  const Base64TestVector &t = GetParam();
   if (t.relation == valid) {
     // The non-canonical encodings will generally have odd whitespace etc
     // that |EVP_DecodeBase64| will reject.
@@ -164,7 +164,7 @@
 }
 
 TEST_P(Base64Test, DecodeBlock) {
-  const TestVector &t = GetParam();
+  const Base64TestVector &t = GetParam();
   if (t.relation != canonical) {
     return;
   }
@@ -188,7 +188,7 @@
 }
 
 TEST_P(Base64Test, EncodeDecode) {
-  const TestVector &t = GetParam();
+  const Base64TestVector &t = GetParam();
 
   EVP_ENCODE_CTX ctx;
   const size_t decoded_len = strlen(t.decoded);
@@ -246,7 +246,7 @@
 }
 
 TEST_P(Base64Test, DecodeUpdateStreaming) {
-  const TestVector &t = GetParam();
+  const Base64TestVector &t = GetParam();
   if (t.relation == invalid) {
     return;
   }
diff --git a/src/crypto/chacha/asm/chacha-armv4.pl b/src/crypto/chacha/asm/chacha-armv4.pl
index 2a9a7d7..06be3a6 100755
--- a/src/crypto/chacha/asm/chacha-armv4.pl
+++ b/src/crypto/chacha/asm/chacha-armv4.pl
@@ -1161,4 +1161,4 @@
 
 	print $_,"\n";
 }
-close 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 7795f2c..02325e7 100755
--- a/src/crypto/chacha/asm/chacha-armv8.pl
+++ b/src/crypto/chacha/asm/chacha-armv8.pl
@@ -1134,4 +1134,4 @@
 
 	print $_,"\n";
 }
-close 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 1d6a4e2..ec1cf80 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;
+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 6be270e..ab8a1f7 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;
+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 1a3d064..54f0b5c 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;
+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 0e32279..5b2b977 100644
--- a/src/crypto/cipher_extra/asm/chacha20_poly1305_x86_64.pl
+++ b/src/crypto/cipher_extra/asm/chacha20_poly1305_x86_64.pl
@@ -2485,4 +2485,4 @@
 ___
 }
 
-close STDOUT;
+close STDOUT or die "error closing STDOUT";
diff --git a/src/crypto/cipher_extra/e_aesccm.c b/src/crypto/cipher_extra/e_aesccm.c
index 3e18659..e9e9e16 100644
--- a/src/crypto/cipher_extra/e_aesccm.c
+++ b/src/crypto/cipher_extra/e_aesccm.c
@@ -1,21 +1,56 @@
-/* Copyright (c) 2018, Google Inc.
+/* ====================================================================
+ * Copyright (c) 2008 The OpenSSL Project.  All rights reserved.
  *
- * 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.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
  *
- * 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. */
+ * 1. Redistributions of source code must retain the above 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 acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+ *
+ * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please contact
+ *    openssl-core@openssl.org.
+ *
+ * 5. Products derived from this software may not be called "OpenSSL"
+ *    nor may "OpenSSL" appear in their names without prior written
+ *    permission of the OpenSSL Project.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+ * EXPRESSED 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 OpenSSL PROJECT OR
+ * ITS 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.
+ * ==================================================================== */
 
 #include <openssl/aead.h>
 
 #include <assert.h>
 
+#include <openssl/cpu.h>
 #include <openssl/cipher.h>
 #include <openssl/err.h>
 #include <openssl/mem.h>
@@ -23,6 +58,213 @@
 #include "../fipsmodule/cipher/internal.h"
 
 
+struct ccm128_context {
+  block128_f block;
+  ctr128_f ctr;
+  unsigned M, L;
+};
+
+struct ccm128_state {
+  union {
+    uint64_t u[2];
+    uint8_t c[16];
+  } nonce, cmac;
+};
+
+static int CRYPTO_ccm128_init(struct ccm128_context *ctx, const AES_KEY *key,
+                              block128_f block, ctr128_f ctr, unsigned M,
+                              unsigned L) {
+  if (M < 4 || M > 16 || (M & 1) != 0 || L < 2 || L > 8) {
+    return 0;
+  }
+  ctx->block = block;
+  ctx->ctr = ctr;
+  ctx->M = M;
+  ctx->L = L;
+  return 1;
+}
+
+static size_t CRYPTO_ccm128_max_input(const struct ccm128_context *ctx) {
+  return ctx->L >= sizeof(size_t) ? (size_t)-1
+                                  : (((size_t)1) << (ctx->L * 8)) - 1;
+}
+
+static int ccm128_init_state(const struct ccm128_context *ctx,
+                             struct ccm128_state *state, const AES_KEY *key,
+                             const uint8_t *nonce, size_t nonce_len,
+                             const uint8_t *aad, size_t aad_len,
+                             size_t plaintext_len) {
+  const block128_f block = ctx->block;
+  const unsigned M = ctx->M;
+  const unsigned L = ctx->L;
+
+  // |L| determines the expected |nonce_len| and the limit for |plaintext_len|.
+  if (plaintext_len > CRYPTO_ccm128_max_input(ctx) ||
+      nonce_len != 15 - L) {
+    return 0;
+  }
+
+  // Assemble the first block for computing the MAC.
+  OPENSSL_memset(state, 0, sizeof(*state));
+  state->nonce.c[0] = (uint8_t)((L - 1) | ((M - 2) / 2) << 3);
+  if (aad_len != 0) {
+    state->nonce.c[0] |= 0x40;  // Set AAD Flag
+  }
+  OPENSSL_memcpy(&state->nonce.c[1], nonce, nonce_len);
+  for (unsigned i = 0; i < L; i++) {
+    state->nonce.c[15 - i] = (uint8_t)(plaintext_len >> (8 * i));
+  }
+
+  (*block)(state->nonce.c, state->cmac.c, key);
+  size_t blocks = 1;
+
+  if (aad_len != 0) {
+    unsigned i;
+    // Cast to u64 to avoid the compiler complaining about invalid shifts.
+    uint64_t aad_len_u64 = aad_len;
+    if (aad_len_u64 < 0x10000 - 0x100) {
+      state->cmac.c[0] ^= (uint8_t)(aad_len_u64 >> 8);
+      state->cmac.c[1] ^= (uint8_t)aad_len_u64;
+      i = 2;
+    } else if (aad_len_u64 <= 0xffffffff) {
+      state->cmac.c[0] ^= 0xff;
+      state->cmac.c[1] ^= 0xfe;
+      state->cmac.c[2] ^= (uint8_t)(aad_len_u64 >> 24);
+      state->cmac.c[3] ^= (uint8_t)(aad_len_u64 >> 16);
+      state->cmac.c[4] ^= (uint8_t)(aad_len_u64 >> 8);
+      state->cmac.c[5] ^= (uint8_t)aad_len_u64;
+      i = 6;
+    } else {
+      state->cmac.c[0] ^= 0xff;
+      state->cmac.c[1] ^= 0xff;
+      state->cmac.c[2] ^= (uint8_t)(aad_len_u64 >> 56);
+      state->cmac.c[3] ^= (uint8_t)(aad_len_u64 >> 48);
+      state->cmac.c[4] ^= (uint8_t)(aad_len_u64 >> 40);
+      state->cmac.c[5] ^= (uint8_t)(aad_len_u64 >> 32);
+      state->cmac.c[6] ^= (uint8_t)(aad_len_u64 >> 24);
+      state->cmac.c[7] ^= (uint8_t)(aad_len_u64 >> 16);
+      state->cmac.c[8] ^= (uint8_t)(aad_len_u64 >> 8);
+      state->cmac.c[9] ^= (uint8_t)aad_len_u64;
+      i = 10;
+    }
+
+    do {
+      for (; i < 16 && aad_len != 0; i++) {
+        state->cmac.c[i] ^= *aad;
+        aad++;
+        aad_len--;
+      }
+      (*block)(state->cmac.c, state->cmac.c, key);
+      blocks++;
+      i = 0;
+    } while (aad_len != 0);
+  }
+
+  // Per RFC 3610, section 2.6, the total number of block cipher operations done
+  // must not exceed 2^61. There are two block cipher operations remaining per
+  // message block, plus one block at the end to encrypt the MAC.
+  size_t remaining_blocks = 2 * ((plaintext_len + 15) / 16) + 1;
+  if (plaintext_len + 15 < plaintext_len ||
+      remaining_blocks + blocks < blocks ||
+      (uint64_t) remaining_blocks + blocks > UINT64_C(1) << 61) {
+    return 0;
+  }
+
+  // Assemble the first block for encrypting and decrypting. The bottom |L|
+  // bytes are replaced with a counter and all bit the encoding of |L| is
+  // cleared in the first byte.
+  state->nonce.c[0] &= 7;
+  return 1;
+}
+
+static int ccm128_encrypt(const struct ccm128_context *ctx,
+                          struct ccm128_state *state, const AES_KEY *key,
+                          uint8_t *out, const uint8_t *in, size_t len) {
+  // The counter for encryption begins at one.
+  for (unsigned i = 0; i < ctx->L; i++) {
+    state->nonce.c[15 - i] = 0;
+  }
+  state->nonce.c[15] = 1;
+
+  uint8_t partial_buf[16];
+  unsigned num = 0;
+  if (ctx->ctr != NULL) {
+    CRYPTO_ctr128_encrypt_ctr32(in, out, len, key, state->nonce.c, partial_buf,
+                                &num, ctx->ctr);
+  } else {
+    CRYPTO_ctr128_encrypt(in, out, len, key, state->nonce.c, partial_buf, &num,
+                          ctx->block);
+  }
+  return 1;
+}
+
+static int ccm128_compute_mac(const struct ccm128_context *ctx,
+                              struct ccm128_state *state, const AES_KEY *key,
+                              uint8_t *out_tag, size_t tag_len,
+                              const uint8_t *in, size_t len) {
+  block128_f block = ctx->block;
+  if (tag_len != ctx->M) {
+    return 0;
+  }
+
+  // Incorporate |in| into the MAC.
+  union {
+    uint64_t u[2];
+    uint8_t c[16];
+  } tmp;
+  while (len >= 16) {
+    OPENSSL_memcpy(tmp.c, in, 16);
+    state->cmac.u[0] ^= tmp.u[0];
+    state->cmac.u[1] ^= tmp.u[1];
+    (*block)(state->cmac.c, state->cmac.c, key);
+    in += 16;
+    len -= 16;
+  }
+  if (len > 0) {
+    for (size_t i = 0; i < len; i++) {
+      state->cmac.c[i] ^= in[i];
+    }
+    (*block)(state->cmac.c, state->cmac.c, key);
+  }
+
+  // Encrypt the MAC with counter zero.
+  for (unsigned i = 0; i < ctx->L; i++) {
+    state->nonce.c[15 - i] = 0;
+  }
+  (*block)(state->nonce.c, tmp.c, key);
+  state->cmac.u[0] ^= tmp.u[0];
+  state->cmac.u[1] ^= tmp.u[1];
+
+  OPENSSL_memcpy(out_tag, state->cmac.c, tag_len);
+  return 1;
+}
+
+static int CRYPTO_ccm128_encrypt(const struct ccm128_context *ctx,
+                                 const AES_KEY *key, uint8_t *out,
+                                 uint8_t *out_tag, size_t tag_len,
+                                 const uint8_t *nonce, size_t nonce_len,
+                                 const uint8_t *in, size_t len,
+                                 const uint8_t *aad, size_t aad_len) {
+  struct ccm128_state state;
+  return ccm128_init_state(ctx, &state, key, nonce, nonce_len, aad, aad_len,
+                           len) &&
+         ccm128_compute_mac(ctx, &state, key, out_tag, tag_len, in, len) &&
+         ccm128_encrypt(ctx, &state, key, out, in, len);
+}
+
+static int CRYPTO_ccm128_decrypt(const struct ccm128_context *ctx,
+                                 const AES_KEY *key, uint8_t *out,
+                                 uint8_t *out_tag, size_t tag_len,
+                                 const uint8_t *nonce, size_t nonce_len,
+                                 const uint8_t *in, size_t len,
+                                 const uint8_t *aad, size_t aad_len) {
+  struct ccm128_state state;
+  return ccm128_init_state(ctx, &state, key, nonce, nonce_len, aad, aad_len,
+                           len) &&
+         ccm128_encrypt(ctx, &state, key, out, in, len) &&
+         ccm128_compute_mac(ctx, &state, key, out_tag, tag_len, out, len);
+}
+
 #define EVP_AEAD_AES_CCM_MAX_TAG_LEN 16
 
 struct aead_aes_ccm_ctx {
@@ -30,7 +272,7 @@
     double align;
     AES_KEY ks;
   } ks;
-  CCM128_CONTEXT ccm;
+  struct ccm128_context ccm;
 };
 
 OPENSSL_STATIC_ASSERT(sizeof(((EVP_AEAD_CTX *)NULL)->state) >=
diff --git a/src/crypto/constant_time_test.cc b/src/crypto/constant_time_test.cc
index 59a7bb1..ae80003 100644
--- a/src/crypto/constant_time_test.cc
+++ b/src/crypto/constant_time_test.cc
@@ -153,3 +153,19 @@
     }
   }
 }
+
+TEST(ConstantTimeTest, ValueBarrier) {
+  for (int i = 0; i < 10; i++) {
+    crypto_word_t word;
+    RAND_bytes(reinterpret_cast<uint8_t *>(&word), sizeof(word));
+    EXPECT_EQ(word, value_barrier_w(word));
+
+    uint32_t u32;
+    RAND_bytes(reinterpret_cast<uint8_t *>(&u32), sizeof(u32));
+    EXPECT_EQ(u32, value_barrier_u32(u32));
+
+    uint64_t u64;
+    RAND_bytes(reinterpret_cast<uint8_t *>(&u64), sizeof(u64));
+    EXPECT_EQ(u64, value_barrier_u64(u64));
+  }
+}
diff --git a/src/crypto/cpu-intel.c b/src/crypto/cpu-intel.c
index 98d8d4e..1621ef6 100644
--- a/src/crypto/cpu-intel.c
+++ b/src/crypto/cpu-intel.c
@@ -148,6 +148,9 @@
   int is_intel = ebx == 0x756e6547 /* Genu */ &&
                  edx == 0x49656e69 /* ineI */ &&
                  ecx == 0x6c65746e /* ntel */;
+  int is_amd = ebx == 0x68747541 /* Auth */ &&
+               edx == 0x69746e65 /* enti */ &&
+               ecx == 0x444d4163 /* cAMD */;
 
   uint32_t extended_features[2] = {0};
   if (num_ids >= 7) {
@@ -158,6 +161,24 @@
 
   OPENSSL_cpuid(&eax, &ebx, &ecx, &edx, 1);
 
+  if (is_amd) {
+    // See https://www.amd.com/system/files/TechDocs/25481.pdf, page 10.
+    const uint32_t base_family = (eax >> 8) & 15;
+
+    uint32_t family = base_family;
+    if (base_family == 0xf) {
+      const uint32_t ext_family = (eax >> 20) & 255;
+      family += ext_family;
+    }
+
+    if (family < 0x17) {
+      // Disable RDRAND on AMD families before 0x17 (Zen) due to reported
+      // failures after suspend.
+      // https://bugzilla.redhat.com/show_bug.cgi?id=1150286
+      ecx &= ~(1u << 30);
+    }
+  }
+
   // Force the hyper-threading bit so that the more conservative path is always
   // chosen.
   edx |= 1u << 28;
diff --git a/src/crypto/digest_extra/digest_test.cc b/src/crypto/digest_extra/digest_test.cc
index 7d07c04..6d02c08 100644
--- a/src/crypto/digest_extra/digest_test.cc
+++ b/src/crypto/digest_extra/digest_test.cc
@@ -55,7 +55,7 @@
 static const MD sha512 = { "SHA512", &EVP_sha512, &SHA512 };
 static const MD md5_sha1 = { "MD5-SHA1", &EVP_md5_sha1, nullptr };
 
-struct TestVector {
+struct DigestTestVector {
   // md is the digest to test.
   const MD &md;
   // input is a NUL-terminated string to hash.
@@ -66,7 +66,7 @@
   const char *expected_hex;
 };
 
-static const TestVector kTestVectors[] = {
+static const DigestTestVector kTestVectors[] = {
     // MD4 tests, from RFC 1320. (crypto/md4 does not provide a
     // one-shot MD4 function.)
     { md4, "", 1, "31d6cfe0d16ae931b73c59d7e0c089c0" },
@@ -144,7 +144,7 @@
       "900150983cd24fb0d6963f7d28e17f72a9993e364706816aba3e25717850c26c9cd0d89d" },
 };
 
-static void CompareDigest(const TestVector *test,
+static void CompareDigest(const DigestTestVector *test,
                           const uint8_t *digest,
                           size_t digest_len) {
   static const char kHexTable[] = "0123456789abcdef";
@@ -159,7 +159,7 @@
   EXPECT_STREQ(test->expected_hex, digest_hex);
 }
 
-static void TestDigest(const TestVector *test) {
+static void TestDigest(const DigestTestVector *test) {
   bssl::ScopedEVP_MD_CTX ctx;
 
   // Test the input provided.
diff --git a/src/crypto/dsa/dsa.c b/src/crypto/dsa/dsa.c
index 288e2c8..51dca7f 100644
--- a/src/crypto/dsa/dsa.c
+++ b/src/crypto/dsa/dsa.c
@@ -558,29 +558,34 @@
 }
 
 DSA_SIG *DSA_do_sign(const uint8_t *digest, size_t digest_len, const DSA *dsa) {
-  BIGNUM *kinv = NULL, *r = NULL, *s = NULL;
-  BIGNUM m;
-  BIGNUM xr;
-  BN_CTX *ctx = NULL;
-  int reason = ERR_R_BN_LIB;
-  DSA_SIG *ret = NULL;
-
-  BN_init(&m);
-  BN_init(&xr);
-
   if (!dsa->p || !dsa->q || !dsa->g) {
-    reason = DSA_R_MISSING_PARAMETERS;
-    goto err;
+    OPENSSL_PUT_ERROR(DSA, DSA_R_MISSING_PARAMETERS);
+    return NULL;
+  }
+
+  // Reject invalid parameters. In particular, the algorithm will infinite loop
+  // if |g| is zero.
+  if (BN_is_zero(dsa->p) || BN_is_zero(dsa->q) || BN_is_zero(dsa->g)) {
+    OPENSSL_PUT_ERROR(DSA, DSA_R_INVALID_PARAMETERS);
+    return NULL;
   }
 
   // We only support DSA keys that are a multiple of 8 bits. (This is a weaker
   // check than the one in |DSA_do_check_signature|, which only allows 160-,
   // 224-, and 256-bit keys.
   if (BN_num_bits(dsa->q) % 8 != 0) {
-    reason = DSA_R_BAD_Q_VALUE;
-    goto err;
+    OPENSSL_PUT_ERROR(DSA, DSA_R_BAD_Q_VALUE);
+    return NULL;
   }
 
+  BIGNUM *kinv = NULL, *r = NULL, *s = NULL;
+  BIGNUM m;
+  BIGNUM xr;
+  BN_CTX *ctx = NULL;
+  DSA_SIG *ret = NULL;
+
+  BN_init(&m);
+  BN_init(&xr);
   s = BN_new();
   if (s == NULL) {
     goto err;
@@ -640,7 +645,7 @@
 
 err:
   if (ret == NULL) {
-    OPENSSL_PUT_ERROR(DSA, reason);
+    OPENSSL_PUT_ERROR(DSA, ERR_R_BN_LIB);
     BN_free(r);
     BN_free(s);
   }
diff --git a/src/crypto/dsa/dsa_test.cc b/src/crypto/dsa/dsa_test.cc
index 295a7fd..4682131 100644
--- a/src/crypto/dsa/dsa_test.cc
+++ b/src/crypto/dsa/dsa_test.cc
@@ -62,6 +62,8 @@
 #include <stdio.h>
 #include <string.h>
 
+#include <vector>
+
 #include <gtest/gtest.h>
 
 #include <openssl/bn.h>
@@ -315,3 +317,18 @@
     ADD_FAILURE() << "Tests failed";
   }
 }
+
+TEST(DSATest, InvalidGroup) {
+  bssl::UniquePtr<DSA> dsa = GetFIPSDSA();
+  ASSERT_TRUE(dsa);
+  BN_zero(dsa->g);
+
+  std::vector<uint8_t> sig(DSA_size(dsa.get()));
+  unsigned sig_len;
+  static const uint8_t kDigest[32] = {0};
+  EXPECT_FALSE(
+      DSA_sign(0, kDigest, sizeof(kDigest), sig.data(), &sig_len, dsa.get()));
+  uint32_t err = ERR_get_error();
+  EXPECT_EQ(ERR_LIB_DSA, ERR_GET_LIB(err));
+  EXPECT_EQ(DSA_R_INVALID_PARAMETERS, ERR_GET_REASON(err));
+}
diff --git a/src/crypto/ec_extra/ec_asn1.c b/src/crypto/ec_extra/ec_asn1.c
index 6e21275..31988f3 100644
--- a/src/crypto/ec_extra/ec_asn1.c
+++ b/src/crypto/ec_extra/ec_asn1.c
@@ -159,8 +159,8 @@
         (point_conversion_form_t)(CBS_data(&public_key)[0] & ~0x01);
   } else {
     // Compute the public key instead.
-    if (!ec_point_mul_scalar(group, &ret->pub_key->raw, &ret->priv_key->scalar,
-                             NULL, NULL)) {
+    if (!ec_point_mul_scalar_base(group, &ret->pub_key->raw,
+                                  &ret->priv_key->scalar)) {
       goto err;
     }
     // Remember the original private-key-only encoding.
diff --git a/src/crypto/ecdh_extra/ecdh_extra.c b/src/crypto/ecdh_extra/ecdh_extra.c
index 1e08099..b8a099a 100644
--- a/src/crypto/ecdh_extra/ecdh_extra.c
+++ b/src/crypto/ecdh_extra/ecdh_extra.c
@@ -95,7 +95,7 @@
   EC_RAW_POINT shared_point;
   uint8_t buf[EC_MAX_BYTES];
   size_t buf_len;
-  if (!ec_point_mul_scalar(group, &shared_point, NULL, &pub_key->raw, priv) ||
+  if (!ec_point_mul_scalar(group, &shared_point, &pub_key->raw, priv) ||
       !ec_point_get_affine_coordinate_bytes(group, buf, NULL, &buf_len,
                                             sizeof(buf), &shared_point)) {
     OPENSSL_PUT_ERROR(ECDH, ECDH_R_POINT_ARITHMETIC_FAILURE);
diff --git a/src/crypto/engine/engine.c b/src/crypto/engine/engine.c
index 875f148..973a57c 100644
--- a/src/crypto/engine/engine.c
+++ b/src/crypto/engine/engine.c
@@ -41,9 +41,10 @@
   return engine;
 }
 
-void ENGINE_free(ENGINE *engine) {
+int ENGINE_free(ENGINE *engine) {
   // Methods are currently required to be static so are not unref'ed.
   OPENSSL_free(engine);
+  return 1;
 }
 
 // set_method takes a pointer to a method and its given size and sets
diff --git a/src/crypto/err/dsa.errordata b/src/crypto/err/dsa.errordata
index 6f4bc13..1cf5206 100644
--- a/src/crypto/err/dsa.errordata
+++ b/src/crypto/err/dsa.errordata
@@ -2,6 +2,7 @@
 DSA,104,BAD_VERSION
 DSA,105,DECODE_ERROR
 DSA,106,ENCODE_ERROR
+DSA,107,INVALID_PARAMETERS
 DSA,101,MISSING_PARAMETERS
 DSA,102,MODULUS_TOO_LARGE
 DSA,103,NEED_NEW_SETUP_VALUES
diff --git a/src/crypto/err/evp.errordata b/src/crypto/err/evp.errordata
index aea4a94..771cd6a 100644
--- a/src/crypto/err/evp.errordata
+++ b/src/crypto/err/evp.errordata
@@ -15,6 +15,7 @@
 EVP,114,INVALID_OPERATION
 EVP,115,INVALID_PADDING_MODE
 EVP,133,INVALID_PARAMETERS
+EVP,134,INVALID_PEER_KEY
 EVP,116,INVALID_PSS_SALTLEN
 EVP,131,INVALID_SIGNATURE
 EVP,117,KEYS_NOT_SET
diff --git a/src/crypto/evp/evp.c b/src/crypto/evp/evp.c
index ed7cc85..0e90b6f 100644
--- a/src/crypto/evp/evp.c
+++ b/src/crypto/evp/evp.c
@@ -200,6 +200,8 @@
       return &dsa_asn1_meth;
     case EVP_PKEY_ED25519:
       return &ed25519_asn1_meth;
+    case EVP_PKEY_X25519:
+      return &x25519_asn1_meth;
     default:
       return NULL;
   }
@@ -330,7 +332,73 @@
   return 1;
 }
 
+EVP_PKEY *EVP_PKEY_new_raw_private_key(int type, ENGINE *unused,
+                                       const uint8_t *in, size_t len) {
+  EVP_PKEY *ret = EVP_PKEY_new();
+  if (ret == NULL ||
+      !EVP_PKEY_set_type(ret, type)) {
+    goto err;
+  }
 
+  if (ret->ameth->set_priv_raw == NULL) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
+    goto err;
+  }
+
+  if (!ret->ameth->set_priv_raw(ret, in, len)) {
+    goto err;
+  }
+
+  return ret;
+
+err:
+  EVP_PKEY_free(ret);
+  return NULL;
+}
+
+EVP_PKEY *EVP_PKEY_new_raw_public_key(int type, ENGINE *unused,
+                                      const uint8_t *in, size_t len) {
+  EVP_PKEY *ret = EVP_PKEY_new();
+  if (ret == NULL ||
+      !EVP_PKEY_set_type(ret, type)) {
+    goto err;
+  }
+
+  if (ret->ameth->set_pub_raw == NULL) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
+    goto err;
+  }
+
+  if (!ret->ameth->set_pub_raw(ret, in, len)) {
+    goto err;
+  }
+
+  return ret;
+
+err:
+  EVP_PKEY_free(ret);
+  return NULL;
+}
+
+int EVP_PKEY_get_raw_private_key(const EVP_PKEY *pkey, uint8_t *out,
+                                 size_t *out_len) {
+  if (pkey->ameth->get_priv_raw == NULL) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
+    return 0;
+  }
+
+  return pkey->ameth->get_priv_raw(pkey, out, out_len);
+}
+
+int EVP_PKEY_get_raw_public_key(const EVP_PKEY *pkey, uint8_t *out,
+                                size_t *out_len) {
+  if (pkey->ameth->get_pub_raw == NULL) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE);
+    return 0;
+  }
+
+  return pkey->ameth->get_pub_raw(pkey, out, out_len);
+}
 
 int EVP_PKEY_cmp_parameters(const EVP_PKEY *a, const EVP_PKEY *b) {
   if (a->type != b->type) {
@@ -361,3 +429,10 @@
 void OpenSSL_add_all_digests(void) {}
 
 void EVP_cleanup(void) {}
+
+int EVP_PKEY_base_id(const EVP_PKEY *pkey) {
+  // OpenSSL has two notions of key type because it supports multiple OIDs for
+  // the same algorithm: NID_rsa vs NID_rsaEncryption and five distinct spelling
+  // of DSA. We do not support these, so the base ID is simply the ID.
+  return EVP_PKEY_id(pkey);
+}
diff --git a/src/crypto/evp/evp_asn1.c b/src/crypto/evp/evp_asn1.c
index d56b93b..fc1dce3 100644
--- a/src/crypto/evp/evp_asn1.c
+++ b/src/crypto/evp/evp_asn1.c
@@ -73,6 +73,7 @@
     &ec_asn1_meth,
     &dsa_asn1_meth,
     &ed25519_asn1_meth,
+    &x25519_asn1_meth,
 };
 
 static int parse_key_type(CBS *cbs, int *out_type) {
diff --git a/src/crypto/evp/evp_ctx.c b/src/crypto/evp/evp_ctx.c
index daa1954..9ca2c55 100644
--- a/src/crypto/evp/evp_ctx.c
+++ b/src/crypto/evp/evp_ctx.c
@@ -67,15 +67,14 @@
 
 
 static const EVP_PKEY_METHOD *const evp_methods[] = {
-  &rsa_pkey_meth,
-  &ec_pkey_meth,
-  &ed25519_pkey_meth,
+    &rsa_pkey_meth,
+    &ec_pkey_meth,
+    &ed25519_pkey_meth,
+    &x25519_pkey_meth,
 };
 
 static const EVP_PKEY_METHOD *evp_pkey_meth_find(int type) {
-  unsigned i;
-
-  for (i = 0; i < sizeof(evp_methods)/sizeof(EVP_PKEY_METHOD*); i++) {
+  for (size_t i = 0; i < sizeof(evp_methods)/sizeof(EVP_PKEY_METHOD*); i++) {
     if (evp_methods[i]->pkey_id == type) {
       return evp_methods[i];
     }
diff --git a/src/crypto/evp/evp_extra_test.cc b/src/crypto/evp/evp_extra_test.cc
index 1f6a61d..f520598 100644
--- a/src/crypto/evp/evp_extra_test.cc
+++ b/src/crypto/evp/evp_extra_test.cc
@@ -555,13 +555,10 @@
       0xa6, 0x23, 0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07, 0x51, 0x1a,
   };
 
-  static const uint8_t kPrivateKey[64] = {
+  static const uint8_t kPrivateKeySeed[32] = {
       0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60, 0xba, 0x84, 0x4a,
       0xf4, 0x92, 0xec, 0x2c, 0xc4, 0x44, 0x49, 0xc5, 0x69, 0x7b, 0x32,
-      0x69, 0x19, 0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae, 0x7f, 0x60, 0xd7,
-      0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, 0xd5, 0x4b, 0xfe, 0xd3,
-      0xc9, 0x64, 0x07, 0x3a, 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23,
-      0x25, 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07, 0x51, 0x1a,
+      0x69, 0x19, 0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae, 0x7f, 0x60,
   };
 
   static const uint8_t kPrivateKeyPKCS8[] = {
@@ -572,10 +569,38 @@
   };
 
   // Create a public key.
-  bssl::UniquePtr<EVP_PKEY> pubkey(EVP_PKEY_new_ed25519_public(kPublicKey));
+  bssl::UniquePtr<EVP_PKEY> pubkey(EVP_PKEY_new_raw_public_key(
+      EVP_PKEY_ED25519, nullptr, kPublicKey, sizeof(kPublicKey)));
   ASSERT_TRUE(pubkey);
   EXPECT_EQ(EVP_PKEY_ED25519, EVP_PKEY_id(pubkey.get()));
 
+  // The public key must be extractable.
+  uint8_t buf[32];
+  size_t len;
+  ASSERT_TRUE(EVP_PKEY_get_raw_public_key(pubkey.get(), nullptr, &len));
+  EXPECT_EQ(len, 32u);
+  ASSERT_TRUE(EVP_PKEY_get_raw_public_key(pubkey.get(), buf, &len));
+  EXPECT_EQ(Bytes(buf, len), Bytes(kPublicKey));
+  // Passing too large of a buffer is okay. The function will still only read
+  // 32 bytes.
+  len = 64;
+  ASSERT_TRUE(EVP_PKEY_get_raw_public_key(pubkey.get(), buf, &len));
+  EXPECT_EQ(Bytes(buf, len), Bytes(kPublicKey));
+  // Passing too small of a buffer is noticed.
+  len = 31;
+  EXPECT_FALSE(EVP_PKEY_get_raw_public_key(pubkey.get(), buf, &len));
+  uint32_t err = ERR_get_error();
+  EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err));
+  EXPECT_EQ(EVP_R_BUFFER_TOO_SMALL, ERR_GET_REASON(err));
+  ERR_clear_error();
+
+  // There is no private key.
+  EXPECT_FALSE(EVP_PKEY_get_raw_private_key(pubkey.get(), nullptr, &len));
+  err = ERR_get_error();
+  EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err));
+  EXPECT_EQ(EVP_R_NOT_A_PRIVATE_KEY, ERR_GET_REASON(err));
+  ERR_clear_error();
+
   // The public key must encode properly.
   bssl::ScopedCBB cbb;
   uint8_t *der;
@@ -589,16 +614,40 @@
   // The public key must gracefully fail to encode as a private key.
   ASSERT_TRUE(CBB_init(cbb.get(), 0));
   EXPECT_FALSE(EVP_marshal_private_key(cbb.get(), pubkey.get()));
-  uint32_t err = ERR_get_error();
+  err = ERR_get_error();
   EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err));
   EXPECT_EQ(EVP_R_NOT_A_PRIVATE_KEY, ERR_GET_REASON(err));
+  ERR_clear_error();
   cbb.Reset();
 
   // Create a private key.
-  bssl::UniquePtr<EVP_PKEY> privkey(EVP_PKEY_new_ed25519_private(kPrivateKey));
+  bssl::UniquePtr<EVP_PKEY> privkey(EVP_PKEY_new_raw_private_key(
+      EVP_PKEY_ED25519, NULL, kPrivateKeySeed, sizeof(kPrivateKeySeed)));
   ASSERT_TRUE(privkey);
   EXPECT_EQ(EVP_PKEY_ED25519, EVP_PKEY_id(privkey.get()));
 
+  // The private key must be extractable.
+  ASSERT_TRUE(EVP_PKEY_get_raw_private_key(privkey.get(), nullptr, &len));
+  EXPECT_EQ(len, 32u);
+  ASSERT_TRUE(EVP_PKEY_get_raw_private_key(privkey.get(), buf, &len));
+  EXPECT_EQ(Bytes(buf, len), Bytes(kPrivateKeySeed));
+  // Passing too large of a buffer is okay. The function will still only read
+  // 32 bytes.
+  len = 64;
+  ASSERT_TRUE(EVP_PKEY_get_raw_private_key(privkey.get(), buf, &len));
+  EXPECT_EQ(Bytes(buf, len), Bytes(kPrivateKeySeed));
+  // Passing too small of a buffer is noticed.
+  len = 31;
+  EXPECT_FALSE(EVP_PKEY_get_raw_private_key(privkey.get(), buf, &len));
+  err = ERR_get_error();
+  EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err));
+  EXPECT_EQ(EVP_R_BUFFER_TOO_SMALL, ERR_GET_REASON(err));
+  ERR_clear_error();
+  // The public key must be extractable.
+  len = 32;
+  ASSERT_TRUE(EVP_PKEY_get_raw_public_key(privkey.get(), buf, &len));
+  EXPECT_EQ(Bytes(buf, len), Bytes(kPublicKey));
+
   // The public key must encode from the private key.
   ASSERT_TRUE(CBB_init(cbb.get(), 0));
   ASSERT_TRUE(EVP_marshal_public_key(cbb.get(), privkey.get()));
@@ -617,7 +666,8 @@
   EXPECT_EQ(1, EVP_PKEY_cmp(pubkey.get(), privkey.get()));
 
   static const uint8_t kZeros[32] = {0};
-  bssl::UniquePtr<EVP_PKEY> pubkey2(EVP_PKEY_new_ed25519_public(kZeros));
+  bssl::UniquePtr<EVP_PKEY> pubkey2(EVP_PKEY_new_raw_public_key(
+      EVP_PKEY_ED25519, nullptr, kZeros, sizeof(kZeros)));
   ASSERT_TRUE(pubkey2);
   EXPECT_EQ(0, EVP_PKEY_cmp(pubkey.get(), pubkey2.get()));
   EXPECT_EQ(0, EVP_PKEY_cmp(privkey.get(), pubkey2.get()));
@@ -627,7 +677,6 @@
   ASSERT_TRUE(
       EVP_DigestSignInit(ctx.get(), nullptr, nullptr, nullptr, privkey.get()));
   EXPECT_FALSE(EVP_DigestSignUpdate(ctx.get(), nullptr, 0));
-  size_t len;
   EXPECT_FALSE(EVP_DigestSignFinal(ctx.get(), nullptr, &len));
   ERR_clear_error();
 
@@ -637,6 +686,18 @@
   EXPECT_FALSE(EVP_DigestVerifyUpdate(ctx.get(), nullptr, 0));
   EXPECT_FALSE(EVP_DigestVerifyFinal(ctx.get(), nullptr, 0));
   ERR_clear_error();
+
+  // The buffer length to |EVP_DigestSign| is an input/output parameter and
+  // should be checked before signing.
+  ctx.Reset();
+  ASSERT_TRUE(
+      EVP_DigestSignInit(ctx.get(), nullptr, nullptr, nullptr, privkey.get()));
+  len = 31;
+  EXPECT_FALSE(EVP_DigestSign(ctx.get(), buf, &len, nullptr /* msg */, 0));
+  err = ERR_get_error();
+  EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err));
+  EXPECT_EQ(EVP_R_BUFFER_TOO_SMALL, ERR_GET_REASON(err));
+  ERR_clear_error();
 }
 
 static void ExpectECGroupOnly(const EVP_PKEY *pkey, int nid) {
@@ -695,3 +756,29 @@
   raw = nullptr;
   ExpectECGroupAndKey(pkey.get(), NID_X9_62_prime256v1);
 }
+
+// Test that |EVP_PKEY_keygen| works for Ed25519.
+TEST(EVPExtraTest, Ed25519Keygen) {
+  bssl::UniquePtr<EVP_PKEY_CTX> pctx(
+      EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr));
+  ASSERT_TRUE(pctx);
+  ASSERT_TRUE(EVP_PKEY_keygen_init(pctx.get()));
+  EVP_PKEY *raw = nullptr;
+  ASSERT_TRUE(EVP_PKEY_keygen(pctx.get(), &raw));
+  bssl::UniquePtr<EVP_PKEY> pkey(raw);
+
+  // Round-trip a signature to sanity-check the key is good.
+  bssl::ScopedEVP_MD_CTX ctx;
+  ASSERT_TRUE(
+      EVP_DigestSignInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get()));
+  uint8_t sig[64];
+  size_t len = sizeof(sig);
+  ASSERT_TRUE(EVP_DigestSign(ctx.get(), sig, &len,
+                             reinterpret_cast<const uint8_t *>("hello"), 5));
+
+  ctx.Reset();
+  ASSERT_TRUE(
+      EVP_DigestVerifyInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get()));
+  ASSERT_TRUE(EVP_DigestVerify(ctx.get(), sig, len,
+                               reinterpret_cast<const uint8_t *>("hello"), 5));
+}
diff --git a/src/crypto/evp/evp_test.cc b/src/crypto/evp/evp_test.cc
index 4d74292..b4be636 100644
--- a/src/crypto/evp/evp_test.cc
+++ b/src/crypto/evp/evp_test.cc
@@ -119,6 +119,9 @@
   if (name == "Ed25519") {
     return EVP_PKEY_ED25519;
   }
+  if (name == "X25519") {
+    return EVP_PKEY_X25519;
+  }
   ADD_FAILURE() << "Unknown key type: " << name;
   return EVP_PKEY_NONE;
 }
@@ -179,7 +182,62 @@
       !t->GetBytes(&output, "Output")) {
     return false;
   }
-  EXPECT_EQ(Bytes(output), Bytes(der, der_len)) << "Re-encoding the key did not match.";
+  EXPECT_EQ(Bytes(output), Bytes(der, der_len))
+      << "Re-encoding the key did not match.";
+
+  if (t->HasAttribute("ExpectNoRawPrivate")) {
+    size_t len;
+    EXPECT_FALSE(EVP_PKEY_get_raw_private_key(pkey.get(), nullptr, &len));
+  } else if (t->HasAttribute("ExpectRawPrivate")) {
+    std::vector<uint8_t> expected;
+    if (!t->GetBytes(&expected, "ExpectRawPrivate")) {
+      return false;
+    }
+
+    std::vector<uint8_t> raw;
+    size_t len;
+    if (!EVP_PKEY_get_raw_private_key(pkey.get(), nullptr, &len)) {
+      return false;
+    }
+    raw.resize(len);
+    if (!EVP_PKEY_get_raw_private_key(pkey.get(), raw.data(), &len)) {
+      return false;
+    }
+    raw.resize(len);
+    EXPECT_EQ(Bytes(raw), Bytes(expected));
+
+    // Short buffers should be rejected.
+    raw.resize(len - 1);
+    len = raw.size();
+    EXPECT_FALSE(EVP_PKEY_get_raw_private_key(pkey.get(), raw.data(), &len));
+  }
+
+  if (t->HasAttribute("ExpectNoRawPublic")) {
+    size_t len;
+    EXPECT_FALSE(EVP_PKEY_get_raw_public_key(pkey.get(), nullptr, &len));
+  } else if (t->HasAttribute("ExpectRawPublic")) {
+    std::vector<uint8_t> expected;
+    if (!t->GetBytes(&expected, "ExpectRawPublic")) {
+      return false;
+    }
+
+    std::vector<uint8_t> raw;
+    size_t len;
+    if (!EVP_PKEY_get_raw_public_key(pkey.get(), nullptr, &len)) {
+      return false;
+    }
+    raw.resize(len);
+    if (!EVP_PKEY_get_raw_public_key(pkey.get(), raw.data(), &len)) {
+      return false;
+    }
+    raw.resize(len);
+    EXPECT_EQ(Bytes(raw), Bytes(expected));
+
+    // Short buffers should be rejected.
+    raw.resize(len - 1);
+    len = raw.size();
+    EXPECT_FALSE(EVP_PKEY_get_raw_public_key(pkey.get(), raw.data(), &len));
+  }
 
   // Save the key for future tests.
   const std::string &key_name = t->GetParameter();
@@ -190,7 +248,7 @@
 
 // SetupContext configures |ctx| based on attributes in |t|, with the exception
 // of the signing digest which must be configured externally.
-static bool SetupContext(FileTest *t, EVP_PKEY_CTX *ctx) {
+static bool SetupContext(FileTest *t, KeyMap *key_map, EVP_PKEY_CTX *ctx) {
   if (t->HasAttribute("RSAPadding")) {
     int padding;
     if (!GetRSAPadding(t, &padding, t->GetAttributeOrDie("RSAPadding")) ||
@@ -230,6 +288,74 @@
     }
     buf.release();
   }
+  if (t->HasAttribute("DerivePeer")) {
+    std::string derive_peer = t->GetAttributeOrDie("DerivePeer");
+    if (key_map->count(derive_peer) == 0) {
+      ADD_FAILURE() << "Could not find key " << derive_peer;
+      return false;
+    }
+    EVP_PKEY *derive_peer_key = (*key_map)[derive_peer].get();
+    if (!EVP_PKEY_derive_set_peer(ctx, derive_peer_key)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+static bool TestDerive(FileTest *t, KeyMap *key_map, EVP_PKEY *key) {
+  bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(key, nullptr));
+  if (!ctx ||
+      !EVP_PKEY_derive_init(ctx.get()) ||
+      !SetupContext(t, key_map, ctx.get())) {
+    return false;
+  }
+
+  bssl::UniquePtr<EVP_PKEY_CTX> copy(EVP_PKEY_CTX_dup(ctx.get()));
+  if (!copy) {
+    return false;
+  }
+
+  for (EVP_PKEY_CTX *pctx : {ctx.get(), copy.get()}) {
+    size_t len;
+    std::vector<uint8_t> actual, output;
+    if (!EVP_PKEY_derive(pctx, nullptr, &len)) {
+      return false;
+    }
+    actual.resize(len);
+    if (!EVP_PKEY_derive(pctx, actual.data(), &len)) {
+      return false;
+    }
+    actual.resize(len);
+
+    // Defer looking up the attribute so Error works properly.
+    if (!t->GetBytes(&output, "Output")) {
+      return false;
+    }
+    EXPECT_EQ(Bytes(output), Bytes(actual));
+
+    // Test when the buffer is too large.
+    actual.resize(len + 1);
+    len = actual.size();
+    if (!EVP_PKEY_derive(pctx, actual.data(), &len)) {
+      return false;
+    }
+    actual.resize(len);
+    EXPECT_EQ(Bytes(output), Bytes(actual));
+
+    // Test when the buffer is too small.
+    actual.resize(len - 1);
+    len = actual.size();
+    if (t->HasAttribute("SmallBufferTruncates")) {
+      if (!EVP_PKEY_derive(pctx, actual.data(), &len)) {
+        return false;
+      }
+      actual.resize(len);
+      EXPECT_EQ(Bytes(output.data(), len), Bytes(actual));
+    } else {
+      EXPECT_FALSE(EVP_PKEY_derive(pctx, actual.data(), &len));
+      ERR_clear_error();
+    }
+  }
   return true;
 }
 
@@ -243,6 +369,14 @@
     return ImportKey(t, key_map, EVP_parse_public_key, EVP_marshal_public_key);
   }
 
+  // Load the key.
+  const std::string &key_name = t->GetParameter();
+  if (key_map->count(key_name) == 0) {
+    ADD_FAILURE() << "Could not find key " << key_name;
+    return false;
+  }
+  EVP_PKEY *key = (*key_map)[key_name].get();
+
   int (*key_op_init)(EVP_PKEY_CTX *ctx) = nullptr;
   int (*key_op)(EVP_PKEY_CTX *ctx, uint8_t *out, size_t *out_len,
                 const uint8_t *in, size_t in_len) = nullptr;
@@ -266,19 +400,13 @@
   } else if (t->GetType() == "Encrypt") {
     key_op_init = EVP_PKEY_encrypt_init;
     key_op = EVP_PKEY_encrypt;
+  } else if (t->GetType() == "Derive") {
+    return TestDerive(t, key_map, key);
   } else {
     ADD_FAILURE() << "Unknown test " << t->GetType();
     return false;
   }
 
-  // Load the key.
-  const std::string &key_name = t->GetParameter();
-  if (key_map->count(key_name) == 0) {
-    ADD_FAILURE() << "Could not find key " << key_name;
-    return false;
-  }
-  EVP_PKEY *key = (*key_map)[key_name].get();
-
   const EVP_MD *digest = nullptr;
   if (t->HasAttribute("Digest")) {
     digest = GetDigest(t, t->GetAttributeOrDie("Digest"));
@@ -300,7 +428,7 @@
     bssl::ScopedEVP_MD_CTX ctx, copy;
     EVP_PKEY_CTX *pctx;
     if (!md_op_init(ctx.get(), &pctx, digest, nullptr, key) ||
-        !SetupContext(t, pctx) ||
+        !SetupContext(t, key_map, pctx) ||
         !EVP_MD_CTX_copy_ex(copy.get(), ctx.get())) {
       return false;
     }
@@ -347,7 +475,7 @@
       !key_op_init(ctx.get()) ||
       (digest != nullptr &&
        !EVP_PKEY_CTX_set_signature_md(ctx.get(), digest)) ||
-      !SetupContext(t, ctx.get())) {
+      !SetupContext(t, key_map, ctx.get())) {
     return false;
   }
 
@@ -381,7 +509,7 @@
           !EVP_PKEY_decrypt_init(decrypt_ctx.get()) ||
           (digest != nullptr &&
            !EVP_PKEY_CTX_set_signature_md(decrypt_ctx.get(), digest)) ||
-          !SetupContext(t, decrypt_ctx.get()) ||
+          !SetupContext(t, key_map, decrypt_ctx.get()) ||
           !EVP_PKEY_decrypt(decrypt_ctx.get(), nullptr, &plaintext_len,
                             actual.data(), actual.size())) {
         return false;
@@ -401,7 +529,7 @@
           !EVP_PKEY_verify_init(verify_ctx.get()) ||
           (digest != nullptr &&
            !EVP_PKEY_CTX_set_signature_md(verify_ctx.get(), digest)) ||
-          !SetupContext(t, verify_ctx.get())) {
+          !SetupContext(t, key_map, verify_ctx.get())) {
         return false;
       }
       if (t->HasAttribute("VerifyPSSSaltLength")) {
diff --git a/src/crypto/evp/evp_tests.txt b/src/crypto/evp/evp_tests.txt
index 9dbe1cb..2ac51d3 100644
--- a/src/crypto/evp/evp_tests.txt
+++ b/src/crypto/evp/evp_tests.txt
@@ -6,11 +6,15 @@
 PrivateKey = RSA-2048
 Type = RSA
 Input = 308204bc020100300d06092a864886f70d0101010500048204a6308204a20201000282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f02030100010282010060297ac7991b167a06d6b24758b8cbe208beb9b2d9ec9738bd80f90a2e35005dd7ce292d9e29ba885bd316fef1f20913bc0ac90d6b0808b2414d82104441d8624a33ce0233c8f780a48b375aff02d76712228a702484db3f9ebecccfbbee1709dba182800d949e9e4216e0bff3558388f8bd90da373a1d82743ec3fbdd1427fd16825a657a316912e8695365117ca2f845c909405fcac55f895fc15d20386c26ee78c9e99075029a178a6c1e4cf0c200e8a9cfb27e9d156f86e6c2adc22b1a84a1cd5ca5b2790875d79407c84b352395cb81cc3fed5bb043b69ede0c07204550025cee8c5f440170b6120bb48e0f747bcd8f522110850df043c428dfd187053102818100f6f961b47cbc035d3aedebc7de850a956b65ecdb9cf60764063f15aa48553c58d972fe6675056e35ddfdc37bf3b9f2f622ee271337256849c9bef2176fe8f7c3f8bb91ba374dd53baf3dec814d2bdec10c1fdc88cdd16876f26b1edfa3f094197edf4d42ff1fb2971103b898ca859c427287086a842ab410bb69cf2d35af6be302818100d47e724a7ff41048b270c2524a4101878b73159bb73d3dbc187b220e635b3534f96e243a184d93f860b6bfbb6b71c1ed9a1e1f458583023c301e96a692c1a08b53d0ec9ca910100d80451e3b7dc6a01bac4aecef8df798846bc235a08cbba2cf4c06804cc11219e95608c714e3f1430d491fadbba32a5751a04f97745834c9a502818021f2452bb9b95dfd028c914bf799f1ca77e89a95d50d3c16d384f8455f8bd7af9eb3dfa3d591d9842def235f7630a8e48c088ff6642e101794535a933e1e976fa8509fc728b2da0c4a1a08d7fcf37abaae1ff3001aca1dc1bbb05d9dffbaa1a09f7fb1eef38237d9ebccc722b9338436dde7119112798c26809c1a8dec4320610281801f7510aa62c2d8de4a3c53282781f41e02d0e8b402ae78432e449c48110161a11403f02d01880a8dcc938152d79721a4711a607ac4471ebf964810f95be47a45e60499e29f4c9773c83773404f606637728c2d0351bb03c326c8bb73a721e7fa5440ea2172bba1465fcc30dcb0d9f89930e815aa1f7f9729a857e00e0338dd590281804d1f0d756fe77e01099a652f50a88b7b685dc5bf00981d5d2376fd0c6fe29cd5b638734479305a73ad3c1599d39eae3bae035fbd6fed07c28de705933879a06e48e6a603686ed8e2560a5f6af1f2c24faf4aa960e382186f15eedce9a2491ae730680dd4cf778b70faa86826ab3223477cc91377b19a6d5a2eaea219760beed5
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # The public half of the same key encoded as a SubjectPublicKeyInfo.
 PublicKey = RSA-2048-SPKI
 Type = RSA
 Input = 30820122300d06092a864886f70d01010105000382010f003082010a0282010100cd0081ea7b2ae1ea06d59f7c73d9ffb94a09615c2e4ba7c636cef08dd3533ec3185525b015c769b99a77d6725bf9c3532a9b6e5f6627d5fb85160768d3dda9cbd35974511717dc3d309d2fc47ee41f97e32adb7f9dd864a1c4767a666ecd71bc1aacf5e7517f4b38594fea9b05e42d5ada9912008013e45316a4d9bb8ed086b88d28758bacaf922d46a868b485d239c9baeb0e2b64592710f42b2d1ea0a4b4802c0becab328f8a68b0073bdb546feea9809d2849912b390c1532bc7e29c7658f8175fae46f34332ff87bcab3e40649b98577869da0ea718353f0722754886913648760d122be676e0fc483dd20ffc31bda96a31966c9aa2e75ad03de47e1c44f0203010001
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # The same key but with a negative RSA modulus.
 PublicKey = RSA-2048-SPKI-Negative
@@ -31,21 +35,29 @@
 PrivateKey = RSA-512
 Type = RSA
 Input = 30820154020100300d06092a864886f70d01010105000482013e3082013a020100024100dd20403d976a38c9d79152d87b5c8e9f05033eadd7b7de709bf5b0c4a5182a97d18483526b02362b992e154a9f37faa396ca2685cdab8fec09877ebe705f4dd70203010001024055bebcca655d7e39de8a6eaa9d636db682161907064039544755c53eeb99ec618c03a210dbc61471eaba10c5c365c9726d6b7a96f54d455f7d168d49367270e1022100f21a05d9fd6817301ce49ce10448f9bdd44f5ef5b7557cd7d83155db46382ae7022100e9d1f7157783db2feab1936954ddc4e83aa365695868144cda1be6813b61d791022100d6001eb0040920860ce41fafdf23ca6dfbdf74e6e9f98cf3164cf5c16f9e727d02206f6f73f4b52b10517be6f9bc5f87fa0a3bb817e2e711636b651f9af1c85d4f21022063eff2e57f5b4ca20342cfe793e25526624e3692f192461f9e1ce7f13f2d72c8
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # RSA 515 bit key.
 PrivateKey = RSA-515
 Type = RSA
 Input = 30820157020100300d06092a864886f70d0101010500048201413082013d0201000241054fa166e205e658bbe8a2dc35311c0c2b75b7e4569fd9642c8bae809279271fc824f26baa1166ea46298ca63379ea76adbada2b61e5066820a35beaec1aca227f020301000102410266c972be0d30e53ac2acb1aa13b4bd0401cccf212452a66b4615f7e943831f67b4ca48560582d0ca886044aaaaf87945252a848c1947944186e6eb83969bf91102210309e631761842cc8a2ccfd372c20a9cba21de1a199c30ab440bc6b51079f4e825022101bf715c1db432627ca7c29a293b9210f2eff1e92d12f306ebaa5334f8ee03dcd30221018ac58a765f2b8f37d434081fe5ff92b81735ead2f263f4968ccf63d61fbe3d0d0221015b247a1159a2d5a25d0db049593c6405f77f3a278c521d066e290c2a2d8fb59d0221026224aa31fd95c14d24fd03b8a195bba4cc88df7c37f5370a5ab19f882f1404d6
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # EC P-256 key
 PrivateKey = P-256
 Type = EC
 Input = 308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02010104208a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb5728725a144034200042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # The same key as above with the optional public key omitted.
 PrivateKey = P-256-MissingPublic
 Type = EC
 Input = 3041020100301306072a8648ce3d020106082a8648ce3d0301070427302502010104208a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb5728725
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # The same key as above with redundant parameters.
 PrivateKey = P-256-ExtraParameters
@@ -53,6 +65,8 @@
 Input = 308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104208a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb5728725a00a06082a8648ce3d030107a144034200042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
 # The key re-encodes with the parameters removed.
 Output = 308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02010104208a872fb62893c4d1ffc5b9f0f91758069f8352e08fa05a49f8db926cb5728725a144034200042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # The same key, but with the redundant parameters in the ECPrivateKey mismatched.
 PrivateKey = P-256-BadInnerParameters
@@ -63,6 +77,8 @@
 PublicKey = P-256-SPKI
 Type = EC
 Input = 3059301306072a8648ce3d020106082a8648ce3d030107034200042c150f429ce70f216c252cf5e062ce1f639cd5d165c7f89424072c27197d78b33b920e95cdb664e990dcf0cfea0d94e2a8e6af9d0e58056e653104925b9fe6c9
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # The same as above, but with the curve explicitly spelled out.
 PublicKey = P-256-SPKI
@@ -78,58 +94,84 @@
 PrivateKey = DSA-1024
 Type = DSA
 Input = 308202650201003082023906072a8648ce3804013082022c02820101009e12fab3de12213501dd82aa10ca2d101d2d4ebfef4d2a3f8daa0fe0cedad8d6af85616aa2f3252c0a2b5a6db09e6f14900e0ddb8311876dd8f9669525f99ed65949e184d5064793271169a228680b95ec12f59a8e20b21f2b58eb2a2012d35bde2ee351822fe8f32d0a330565dcce5c672b7259c14b2433d0b5b2ca2b2db0ab626e8f13f47fe0345d904e7294bb038e9ce21a9e580b83356278706cfe768436c69de149ccff98b4aab8cb4f6385c9f102ce59346eaeef27e0ad222d53d6e89cc8cde5776dd00057b03f2d88ab3cedbafd7b585f0b7f7835e17a3728bbf25ea62572f245dc111f3ce39cb6ffacc31b0a2790e7bde90224ea9b09315362af3d2b022100f381dcf53ebf724f8b2e5ca82c010fb4b5eda9358d0fd88ed278589488b54fc3028201000c402a725dcc3a62e02bf4cf43cd17f4a493591220223669cf4193edab423ad08dfb552e308a6a57a5ffbc7cd0fb2087f81f8df0cb08ab2133287d2b6968714a94f633c940845a48a3e16708dde761cc6a8eab2d84db21b6ea5b07681493cc9c31fbc368b243f6ddf8c932a8b4038f44e7b15ca876344a147859f2b43b39458668ad5e0a1a9a669546dd2812e3b3617a0aef99d58e3bb4cc87fd94225e01d2dcc469a77268146c51918f18e8b4d70aa1f0c7623bcc52cf3731d38641b2d2830b7eecb2f09552ff137d046e494e7f33c3590002b16d1b97d936fda28f90c3ed3ca35338168ac16f77c3c57adc2e8f7c6c2256e41a5f65450590dbb5bcf06d66610423022100b0c768702743bc51242993a971a52889795444f7c6452203d0ce84fe6117d46e
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # A DSA public key.
 PublicKey = DSA-1024-SPKI
 Type = DSA
 Input = 308201b73082012c06072a8648ce3804013082011f02818100b3429b8b128c9079f9b72e86857e98d265e5d91661ed8b5f4cc56e5eed1e571da30186983a9dd76297eab73ee13a1db841f8800d04a7cab478af6cde2ea4a2868531af169a24858c6268efa39ceb7ed0d4227eb5bbb01124a2a5a26038c7bcfb8cc827f68f5202345166e4718596799b65c9def82828ce44e62e38e41a0d24b1021500c5a56c81ddd87f47e676546c56d05706421624cf0281810094de40d27314fe929e47ff9b1ac65cfc73ef38c4d381c890be6217b15039ae18190e6b421af8c0bda35a5cfd050f58ae2644adce83e68c8e5ba11729df56bbb21e227a60b816cc033fa799a38fe1ba5b4aa1801b6f841ce3df99feb3b4fb96950c960af13fa2ce920aabc12dd24ad2044a35063ea0e25f67f560f4cfbdc5598303818400028180258c30ebbb7f34fdc873ce679f6cea373c7886d75d4421b90920db034daedd292c64d8edd8cdbdd7f3ad23d74cfa2135247d0cef6ecf2e14f99e19d22a8c1266bd8fb8719c0e5667c716c45c7adbdabe548085bdad2dfee636f8d52fd6adb2193df6c4f0520fbd171b91882e0e4f321f8250ffecf4dbea00e114427d3ef96c1a
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # The same key as above, but without the parameters.
 PublicKey = DSA-1024-SPKI-No-Params
 Type = DSA
 Input = 308192300906072a8648ce38040103818400028180258c30ebbb7f34fdc873ce679f6cea373c7886d75d4421b90920db034daedd292c64d8edd8cdbdd7f3ad23d74cfa2135247d0cef6ecf2e14f99e19d22a8c1266bd8fb8719c0e5667c716c45c7adbdabe548085bdad2dfee636f8d52fd6adb2193df6c4f0520fbd171b91882e0e4f321f8250ffecf4dbea00e114427d3ef96c1a
+ExpectNoRawPrivate
+ExpectNoRawPublic
 
 # Private keys from RFC 8032.
 PrivateKey = Ed25519
 Type = Ed25519
 Input = 302e020100300506032b6570042204209d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60
+ExpectRawPrivate = 9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60
+ExpectRawPublic = d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a
 
 PrivateKey = Ed25519-2
 Type = Ed25519
 Input = 302e020100300506032b6570042204204ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb
+ExpectRawPrivate = 4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb
+ExpectRawPublic = 3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c
 
 PrivateKey = Ed25519-3
 Type = Ed25519
 Input = 302e020100300506032b657004220420c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7
+ExpectRawPrivate = c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7
+ExpectRawPublic = fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025
 
 PrivateKey = Ed25519-4
 Type = Ed25519
 Input = 302e020100300506032b657004220420f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5
+ExpectRawPrivate = f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5
+ExpectRawPublic = 278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e
 
 PrivateKey = Ed25519-5
 Type = Ed25519
 Input = 302e020100300506032b657004220420833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42
+ExpectRawPrivate = 833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42
+ExpectRawPublic = ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf
 
 # Public keys from RFC 8032.
 PublicKey = Ed25519-SPKI
 Type = Ed25519
 Input = 302a300506032b6570032100d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a
+ExpectNoRawPrivate
+ExpectRawPublic = d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a
 
 PublicKey = Ed25519-SPKI-2
 Type = Ed25519
 Input = 302a300506032b65700321003d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c
+ExpectNoRawPrivate
+ExpectRawPublic = 3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c
 
 PublicKey = Ed25519-SPKI-3
 Type = Ed25519
 Input = 302a300506032b6570032100fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025
+ExpectNoRawPrivate
+ExpectRawPublic = fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025
 
 PublicKey = Ed25519-SPKI-4
 Type = Ed25519
 Input = 302a300506032b6570032100278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e
+ExpectNoRawPrivate
+ExpectRawPublic = 278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e
 
 PublicKey = Ed25519-SPKI-5
 Type = Ed25519
 Input = 302a300506032b6570032100ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf
+ExpectNoRawPrivate
+ExpectRawPublic = ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf
 
 # The first key, private and public, with invalid NULL parameters.
 PrivateKey = Ed25519-NULL
@@ -1565,3 +1607,42 @@
 Input = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
 Output = e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b
 Error = OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE
+
+
+# Derive tests.
+
+PrivateKey = ECDH-P256-Private
+Type = EC
+Input = 3041020100301306072a8648ce3d020106082a8648ce3d0301070427302502010104207d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534
+
+PublicKey = ECDH-P256-Peer
+Type = EC
+Input = 3059301306072a8648ce3d020106082a8648ce3d03010703420004700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac
+
+Derive = ECDH-P256-Private
+DerivePeer = ECDH-P256-Peer
+Output = 46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b
+SmallBufferTruncates
+
+PrivateKey = X25519-Private
+Type = X25519
+Input = 302e020100300506032b656e04220420a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4
+ExpectRawPrivate = a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4
+
+PublicKey = X25519-Peer
+Type = X25519
+Input = 302a300506032b656e032100e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c
+ExpectRawPublic = e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c
+
+PublicKey = X25519-SmallOrderPeer
+Type = X25519
+ExpectRawPublic = e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800
+Input = 302a300506032b656e032100e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800
+
+Derive = X25519-Private
+DerivePeer = X25519-Peer
+Output = c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552
+
+Derive = X25519-Private
+DerivePeer = X25519-SmallOrderPeer
+Error = INVALID_PEER_KEY
diff --git a/src/crypto/evp/internal.h b/src/crypto/evp/internal.h
index 43847ea..8b6a583 100644
--- a/src/crypto/evp/internal.h
+++ b/src/crypto/evp/internal.h
@@ -96,6 +96,11 @@
   // |out|. It returns one on success and zero on error.
   int (*priv_encode)(CBB *out, const EVP_PKEY *key);
 
+  int (*set_priv_raw)(EVP_PKEY *pkey, const uint8_t *in, size_t len);
+  int (*set_pub_raw)(EVP_PKEY *pkey, const uint8_t *in, size_t len);
+  int (*get_priv_raw)(const EVP_PKEY *pkey, uint8_t *out, size_t *out_len);
+  int (*get_pub_raw)(const EVP_PKEY *pkey, uint8_t *out, size_t *out_len);
+
   // pkey_opaque returns 1 if the |pk| is opaque. Opaque keys are backed by
   // custom implementations which do not expose key material and parameters.
   int (*pkey_opaque)(const EVP_PKEY *pk);
@@ -239,14 +244,22 @@
   char has_private;
 } ED25519_KEY;
 
+typedef struct {
+  uint8_t pub[32];
+  uint8_t priv[32];
+  char has_private;
+} X25519_KEY;
+
 extern const EVP_PKEY_ASN1_METHOD dsa_asn1_meth;
 extern const EVP_PKEY_ASN1_METHOD ec_asn1_meth;
 extern const EVP_PKEY_ASN1_METHOD rsa_asn1_meth;
 extern const EVP_PKEY_ASN1_METHOD ed25519_asn1_meth;
+extern const EVP_PKEY_ASN1_METHOD x25519_asn1_meth;
 
 extern const EVP_PKEY_METHOD rsa_pkey_meth;
 extern const EVP_PKEY_METHOD ec_pkey_meth;
 extern const EVP_PKEY_METHOD ed25519_pkey_meth;
+extern const EVP_PKEY_METHOD x25519_pkey_meth;
 
 
 #if defined(__cplusplus)
diff --git a/src/crypto/evp/p_dsa_asn1.c b/src/crypto/evp/p_dsa_asn1.c
index 34b2e70..d50e0fc 100644
--- a/src/crypto/evp/p_dsa_asn1.c
+++ b/src/crypto/evp/p_dsa_asn1.c
@@ -255,6 +255,11 @@
   dsa_priv_decode,
   dsa_priv_encode,
 
+  NULL /* set_priv_raw */,
+  NULL /* set_pub_raw */,
+  NULL /* get_priv_raw */,
+  NULL /* get_pub_raw */,
+
   NULL /* pkey_opaque */,
 
   int_dsa_size,
diff --git a/src/crypto/evp/p_ec_asn1.c b/src/crypto/evp/p_ec_asn1.c
index 0ad8f38..dedc5e0 100644
--- a/src/crypto/evp/p_ec_asn1.c
+++ b/src/crypto/evp/p_ec_asn1.c
@@ -237,6 +237,11 @@
   eckey_priv_decode,
   eckey_priv_encode,
 
+  NULL /* set_priv_raw */,
+  NULL /* set_pub_raw */,
+  NULL /* get_priv_raw */,
+  NULL /* get_pub_raw */,
+
   eckey_opaque,
 
   int_ec_size,
diff --git a/src/crypto/evp/p_ed25519.c b/src/crypto/evp/p_ed25519.c
index 062ea45..9149afb 100644
--- a/src/crypto/evp/p_ed25519.c
+++ b/src/crypto/evp/p_ed25519.c
@@ -16,6 +16,7 @@
 
 #include <openssl/curve25519.h>
 #include <openssl/err.h>
+#include <openssl/mem.h>
 
 #include "internal.h"
 
@@ -23,6 +24,27 @@
 // Ed25519 has no parameters to copy.
 static int pkey_ed25519_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) { return 1; }
 
+static int pkey_ed25519_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) {
+  ED25519_KEY *key = OPENSSL_malloc(sizeof(ED25519_KEY));
+  if (key == NULL) {
+    OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  if (!EVP_PKEY_set_type(pkey, EVP_PKEY_ED25519)) {
+    OPENSSL_free(key);
+    return 0;
+  }
+
+  uint8_t pubkey_unused[32];
+  ED25519_keypair(pubkey_unused, key->key.priv);
+  key->has_private = 1;
+
+  OPENSSL_free(pkey->pkey.ptr);
+  pkey->pkey.ptr = key;
+  return 1;
+}
+
 static int pkey_ed25519_sign_message(EVP_PKEY_CTX *ctx, uint8_t *sig,
                                      size_t *siglen, const uint8_t *tbs,
                                      size_t tbslen) {
@@ -32,12 +54,22 @@
     return 0;
   }
 
-  *siglen = 64;
   if (sig == NULL) {
+    *siglen = 64;
     return 1;
   }
 
-  return ED25519_sign(sig, tbs, tbslen, key->key.priv);
+  if (*siglen < 64) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+
+  if (!ED25519_sign(sig, tbs, tbslen, key->key.priv)) {
+    return 0;
+  }
+
+  *siglen = 64;
+  return 1;
 }
 
 static int pkey_ed25519_verify_message(EVP_PKEY_CTX *ctx, const uint8_t *sig,
@@ -58,7 +90,7 @@
     NULL /* init */,
     pkey_ed25519_copy,
     NULL /* cleanup */,
-    NULL /* keygen */,
+    pkey_ed25519_keygen,
     NULL /* sign */,
     pkey_ed25519_sign_message,
     NULL /* verify */,
diff --git a/src/crypto/evp/p_ed25519_asn1.c b/src/crypto/evp/p_ed25519_asn1.c
index ba08c6d..1f996cf 100644
--- a/src/crypto/evp/p_ed25519_asn1.c
+++ b/src/crypto/evp/p_ed25519_asn1.c
@@ -28,45 +28,101 @@
   pkey->pkey.ptr = NULL;
 }
 
-static int set_pubkey(EVP_PKEY *pkey, const uint8_t pubkey[32]) {
+static int ed25519_set_priv_raw(EVP_PKEY *pkey, const uint8_t *in, size_t len) {
+  if (len != 32) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return 0;
+  }
+
   ED25519_KEY *key = OPENSSL_malloc(sizeof(ED25519_KEY));
   if (key == NULL) {
     OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE);
     return 0;
   }
-  key->has_private = 0;
-  OPENSSL_memcpy(key->key.pub.value, pubkey, 32);
+
+  // The RFC 8032 encoding stores only the 32-byte seed, so we must recover the
+  // full representation which we use from it.
+  uint8_t pubkey_unused[32];
+  ED25519_keypair_from_seed(pubkey_unused, key->key.priv, in);
+  key->has_private = 1;
 
   ed25519_free(pkey);
   pkey->pkey.ptr = key;
   return 1;
 }
 
-static int set_privkey(EVP_PKEY *pkey, const uint8_t privkey[64]) {
+static int ed25519_set_pub_raw(EVP_PKEY *pkey, const uint8_t *in, size_t len) {
+  if (len != 32) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return 0;
+  }
+
   ED25519_KEY *key = OPENSSL_malloc(sizeof(ED25519_KEY));
   if (key == NULL) {
     OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE);
     return 0;
   }
-  key->has_private = 1;
-  OPENSSL_memcpy(key->key.priv, privkey, 64);
+
+  OPENSSL_memcpy(key->key.pub.value, in, 32);
+  key->has_private = 0;
 
   ed25519_free(pkey);
   pkey->pkey.ptr = key;
   return 1;
 }
 
+static int ed25519_get_priv_raw(const EVP_PKEY *pkey, uint8_t *out,
+                                size_t *out_len) {
+  const ED25519_KEY *key = pkey->pkey.ptr;
+  if (!key->has_private) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY);
+    return 0;
+  }
+
+  if (out == NULL) {
+    *out_len = 32;
+    return 1;
+  }
+
+  if (*out_len < 32) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+
+  // The raw private key format is the first 32 bytes of the private key.
+  OPENSSL_memcpy(out, key->key.priv, 32);
+  *out_len = 32;
+  return 1;
+}
+
+static int ed25519_get_pub_raw(const EVP_PKEY *pkey, uint8_t *out,
+                               size_t *out_len) {
+  const ED25519_KEY *key = pkey->pkey.ptr;
+  if (out == NULL) {
+    *out_len = 32;
+    return 1;
+  }
+
+  if (*out_len < 32) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+
+  OPENSSL_memcpy(out, key->key.pub.value, 32);
+  *out_len = 32;
+  return 1;
+}
+
 static int ed25519_pub_decode(EVP_PKEY *out, CBS *params, CBS *key) {
   // See RFC 8410, section 4.
 
   // The parameters must be omitted. Public keys have length 32.
-  if (CBS_len(params) != 0 ||
-      CBS_len(key) != 32) {
+  if (CBS_len(params) != 0) {
     OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
     return 0;
   }
 
-  return set_pubkey(out, CBS_data(key));
+  return ed25519_set_pub_raw(out, CBS_data(key), CBS_len(key));
 }
 
 static int ed25519_pub_encode(CBB *out, const EVP_PKEY *pkey) {
@@ -103,17 +159,12 @@
   CBS inner;
   if (CBS_len(params) != 0 ||
       !CBS_get_asn1(key, &inner, CBS_ASN1_OCTETSTRING) ||
-      CBS_len(key) != 0 ||
-      CBS_len(&inner) != 32) {
+      CBS_len(key) != 0) {
     OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
     return 0;
   }
 
-  // The PKCS#8 encoding stores only the 32-byte seed, so we must recover the
-  // full representation which we use from it.
-  uint8_t pubkey[32], privkey[64];
-  ED25519_keypair_from_seed(pubkey, privkey, CBS_data(&inner));
-  return set_privkey(out, privkey);
+  return ed25519_set_priv_raw(out, CBS_data(&inner), CBS_len(&inner));
 }
 
 static int ed25519_priv_encode(CBB *out, const EVP_PKEY *pkey) {
@@ -145,7 +196,7 @@
 
 static int ed25519_size(const EVP_PKEY *pkey) { return 64; }
 
-static int ed25519_bits(const EVP_PKEY *pkey) { return 256; }
+static int ed25519_bits(const EVP_PKEY *pkey) { return 253; }
 
 const EVP_PKEY_ASN1_METHOD ed25519_asn1_meth = {
     EVP_PKEY_ED25519,
@@ -156,6 +207,10 @@
     ed25519_pub_cmp,
     ed25519_priv_decode,
     ed25519_priv_encode,
+    ed25519_set_priv_raw,
+    ed25519_set_pub_raw,
+    ed25519_get_priv_raw,
+    ed25519_get_pub_raw,
     NULL /* pkey_opaque */,
     ed25519_size,
     ed25519_bits,
@@ -164,27 +219,3 @@
     NULL /* param_cmp */,
     ed25519_free,
 };
-
-EVP_PKEY *EVP_PKEY_new_ed25519_public(const uint8_t public_key[32]) {
-  EVP_PKEY *ret = EVP_PKEY_new();
-  if (ret == NULL ||
-      !EVP_PKEY_set_type(ret, EVP_PKEY_ED25519) ||
-      !set_pubkey(ret, public_key)) {
-    EVP_PKEY_free(ret);
-    return NULL;
-  }
-
-  return ret;
-}
-
-EVP_PKEY *EVP_PKEY_new_ed25519_private(const uint8_t private_key[64]) {
-  EVP_PKEY *ret = EVP_PKEY_new();
-  if (ret == NULL ||
-      !EVP_PKEY_set_type(ret, EVP_PKEY_ED25519) ||
-      !set_privkey(ret, private_key)) {
-    EVP_PKEY_free(ret);
-    return NULL;
-  }
-
-  return ret;
-}
diff --git a/src/crypto/evp/p_rsa_asn1.c b/src/crypto/evp/p_rsa_asn1.c
index 85f6fc8..c097103 100644
--- a/src/crypto/evp/p_rsa_asn1.c
+++ b/src/crypto/evp/p_rsa_asn1.c
@@ -178,6 +178,11 @@
   rsa_priv_decode,
   rsa_priv_encode,
 
+  NULL /* set_priv_raw */,
+  NULL /* set_pub_raw */,
+  NULL /* get_priv_raw */,
+  NULL /* get_pub_raw */,
+
   rsa_opaque,
 
   int_rsa_size,
diff --git a/src/crypto/evp/p_x25519.c b/src/crypto/evp/p_x25519.c
new file mode 100644
index 0000000..ed7df39
--- /dev/null
+++ b/src/crypto/evp/p_x25519.c
@@ -0,0 +1,110 @@
+/* Copyright (c) 2019, 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. */
+
+#include <openssl/evp.h>
+
+#include <openssl/curve25519.h>
+#include <openssl/err.h>
+#include <openssl/mem.h>
+
+#include "internal.h"
+
+
+// X25519 has no parameters to copy.
+static int pkey_x25519_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) { return 1; }
+
+static int pkey_x25519_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) {
+  X25519_KEY *key = OPENSSL_malloc(sizeof(X25519_KEY));
+  if (key == NULL) {
+    OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  if (!EVP_PKEY_set_type(pkey, EVP_PKEY_X25519)) {
+    OPENSSL_free(key);
+    return 0;
+  }
+
+  X25519_keypair(key->pub, key->priv);
+  key->has_private = 1;
+
+  OPENSSL_free(pkey->pkey.ptr);
+  pkey->pkey.ptr = key;
+  return 1;
+}
+
+static int pkey_x25519_derive(EVP_PKEY_CTX *ctx, uint8_t *out,
+                              size_t *out_len) {
+  if (ctx->pkey == NULL || ctx->peerkey == NULL) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_KEYS_NOT_SET);
+    return 0;
+  }
+
+  const X25519_KEY *our_key = ctx->pkey->pkey.ptr;
+  const X25519_KEY *peer_key = ctx->peerkey->pkey.ptr;
+  if (our_key == NULL || peer_key == NULL) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_KEYS_NOT_SET);
+    return 0;
+  }
+
+  if (!our_key->has_private) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY);
+    return 0;
+  }
+
+  if (out != NULL) {
+    if (*out_len < 32) {
+      OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL);
+      return 0;
+    }
+    if (!X25519(out, our_key->priv, peer_key->pub)) {
+      OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PEER_KEY);
+      return 0;
+    }
+  }
+
+  *out_len = 32;
+  return 1;
+}
+
+static int pkey_x25519_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2) {
+  switch (type) {
+    case EVP_PKEY_CTRL_PEER_KEY:
+      // |EVP_PKEY_derive_set_peer| requires the key implement this command,
+      // even if it is a no-op.
+      return 1;
+
+    default:
+      OPENSSL_PUT_ERROR(EVP, EVP_R_COMMAND_NOT_SUPPORTED);
+      return 0;
+  }
+}
+
+const EVP_PKEY_METHOD x25519_pkey_meth = {
+    EVP_PKEY_X25519,
+    NULL /* init */,
+    pkey_x25519_copy,
+    NULL /* cleanup */,
+    pkey_x25519_keygen,
+    NULL /* sign */,
+    NULL /* sign_message */,
+    NULL /* verify */,
+    NULL /* verify_message */,
+    NULL /* verify_recover */,
+    NULL /* encrypt */,
+    NULL /* decrypt */,
+    pkey_x25519_derive,
+    NULL /* paramgen */,
+    pkey_x25519_ctrl,
+};
diff --git a/src/crypto/evp/p_x25519_asn1.c b/src/crypto/evp/p_x25519_asn1.c
new file mode 100644
index 0000000..9025623
--- /dev/null
+++ b/src/crypto/evp/p_x25519_asn1.c
@@ -0,0 +1,249 @@
+/* Copyright (c) 2019, 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. */
+
+#include <openssl/evp.h>
+
+#include <openssl/buf.h>
+#include <openssl/bytestring.h>
+#include <openssl/curve25519.h>
+#include <openssl/err.h>
+#include <openssl/mem.h>
+
+#include "internal.h"
+#include "../internal.h"
+
+
+static void x25519_free(EVP_PKEY *pkey) {
+  OPENSSL_free(pkey->pkey.ptr);
+  pkey->pkey.ptr = NULL;
+}
+
+static int x25519_set_priv_raw(EVP_PKEY *pkey, const uint8_t *in, size_t len) {
+  if (len != 32) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return 0;
+  }
+
+  X25519_KEY *key = OPENSSL_malloc(sizeof(X25519_KEY));
+  if (key == NULL) {
+    OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  OPENSSL_memcpy(key->priv, in, 32);
+  X25519_public_from_private(key->pub, key->priv);
+  key->has_private = 1;
+
+  x25519_free(pkey);
+  pkey->pkey.ptr = key;
+  return 1;
+}
+
+static int x25519_set_pub_raw(EVP_PKEY *pkey, const uint8_t *in, size_t len) {
+  if (len != 32) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return 0;
+  }
+
+  X25519_KEY *key = OPENSSL_malloc(sizeof(X25519_KEY));
+  if (key == NULL) {
+    OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  OPENSSL_memcpy(key->pub, in, 32);
+  key->has_private = 0;
+
+  x25519_free(pkey);
+  pkey->pkey.ptr = key;
+  return 1;
+}
+
+static int x25519_get_priv_raw(const EVP_PKEY *pkey, uint8_t *out,
+                                size_t *out_len) {
+  const X25519_KEY *key = pkey->pkey.ptr;
+  if (!key->has_private) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY);
+    return 0;
+  }
+
+  if (out == NULL) {
+    *out_len = 32;
+    return 1;
+  }
+
+  if (*out_len < 32) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+
+  OPENSSL_memcpy(out, key->priv, 32);
+  *out_len = 32;
+  return 1;
+}
+
+static int x25519_get_pub_raw(const EVP_PKEY *pkey, uint8_t *out,
+                               size_t *out_len) {
+  const X25519_KEY *key = pkey->pkey.ptr;
+  if (out == NULL) {
+    *out_len = 32;
+    return 1;
+  }
+
+  if (*out_len < 32) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL);
+    return 0;
+  }
+
+  OPENSSL_memcpy(out, key->pub, 32);
+  *out_len = 32;
+  return 1;
+}
+
+static int x25519_pub_decode(EVP_PKEY *out, CBS *params, CBS *key) {
+  // See RFC 8410, section 4.
+
+  // The parameters must be omitted. Public keys have length 32.
+  if (CBS_len(params) != 0) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return 0;
+  }
+
+  return x25519_set_pub_raw(out, CBS_data(key), CBS_len(key));
+}
+
+static int x25519_pub_encode(CBB *out, const EVP_PKEY *pkey) {
+  const X25519_KEY *key = pkey->pkey.ptr;
+
+  // See RFC 8410, section 4.
+  CBB spki, algorithm, oid, key_bitstring;
+  if (!CBB_add_asn1(out, &spki, CBS_ASN1_SEQUENCE) ||
+      !CBB_add_asn1(&spki, &algorithm, CBS_ASN1_SEQUENCE) ||
+      !CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) ||
+      !CBB_add_bytes(&oid, x25519_asn1_meth.oid, x25519_asn1_meth.oid_len) ||
+      !CBB_add_asn1(&spki, &key_bitstring, CBS_ASN1_BITSTRING) ||
+      !CBB_add_u8(&key_bitstring, 0 /* padding */) ||
+      !CBB_add_bytes(&key_bitstring, key->pub, 32) ||
+      !CBB_flush(out)) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int x25519_pub_cmp(const EVP_PKEY *a, const EVP_PKEY *b) {
+  const X25519_KEY *a_key = a->pkey.ptr;
+  const X25519_KEY *b_key = b->pkey.ptr;
+  return OPENSSL_memcmp(a_key->pub, b_key->pub, 32) == 0;
+}
+
+static int x25519_priv_decode(EVP_PKEY *out, CBS *params, CBS *key) {
+  // See RFC 8410, section 7.
+
+  // Parameters must be empty. The key is a 32-byte value wrapped in an extra
+  // OCTET STRING layer.
+  CBS inner;
+  if (CBS_len(params) != 0 ||
+      !CBS_get_asn1(key, &inner, CBS_ASN1_OCTETSTRING) ||
+      CBS_len(key) != 0) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR);
+    return 0;
+  }
+
+  return x25519_set_priv_raw(out, CBS_data(&inner), CBS_len(&inner));
+}
+
+static int x25519_priv_encode(CBB *out, const EVP_PKEY *pkey) {
+  X25519_KEY *key = pkey->pkey.ptr;
+  if (!key->has_private) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY);
+    return 0;
+  }
+
+  // See RFC 8410, section 7.
+  CBB pkcs8, algorithm, oid, private_key, inner;
+  if (!CBB_add_asn1(out, &pkcs8, CBS_ASN1_SEQUENCE) ||
+      !CBB_add_asn1_uint64(&pkcs8, 0 /* version */) ||
+      !CBB_add_asn1(&pkcs8, &algorithm, CBS_ASN1_SEQUENCE) ||
+      !CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) ||
+      !CBB_add_bytes(&oid, x25519_asn1_meth.oid, x25519_asn1_meth.oid_len) ||
+      !CBB_add_asn1(&pkcs8, &private_key, CBS_ASN1_OCTETSTRING) ||
+      !CBB_add_asn1(&private_key, &inner, CBS_ASN1_OCTETSTRING) ||
+      // The PKCS#8 encoding stores only the 32-byte seed which is the first 32
+      // bytes of the private key.
+      !CBB_add_bytes(&inner, key->priv, 32) ||
+      !CBB_flush(out)) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int x25519_size(const EVP_PKEY *pkey) { return 32; }
+
+static int x25519_bits(const EVP_PKEY *pkey) { return 253; }
+
+const EVP_PKEY_ASN1_METHOD x25519_asn1_meth = {
+    EVP_PKEY_X25519,
+    {0x2b, 0x65, 0x6e},
+    3,
+    x25519_pub_decode,
+    x25519_pub_encode,
+    x25519_pub_cmp,
+    x25519_priv_decode,
+    x25519_priv_encode,
+    x25519_set_priv_raw,
+    x25519_set_pub_raw,
+    x25519_get_priv_raw,
+    x25519_get_pub_raw,
+    NULL /* pkey_opaque */,
+    x25519_size,
+    x25519_bits,
+    NULL /* param_missing */,
+    NULL /* param_copy */,
+    NULL /* param_cmp */,
+    x25519_free,
+};
+
+int EVP_PKEY_set1_tls_encodedpoint(EVP_PKEY *pkey, const uint8_t *in,
+                                   size_t len) {
+  // TODO(davidben): In OpenSSL, this function also works for |EVP_PKEY_EC|
+  // keys. Add support if it ever comes up.
+  if (pkey->type != EVP_PKEY_X25519) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_PUBLIC_KEY_TYPE);
+    return 0;
+  }
+
+  return x25519_set_pub_raw(pkey, in, len);
+}
+
+size_t EVP_PKEY_get1_tls_encodedpoint(const EVP_PKEY *pkey, uint8_t **out_ptr) {
+  // TODO(davidben): In OpenSSL, this function also works for |EVP_PKEY_EC|
+  // keys. Add support if it ever comes up.
+  if (pkey->type != EVP_PKEY_X25519) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_PUBLIC_KEY_TYPE);
+    return 0;
+  }
+
+  const X25519_KEY *key = pkey->pkey.ptr;
+  if (key == NULL) {
+    OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET);
+    return 0;
+  }
+
+  *out_ptr = BUF_memdup(key->pub, 32);
+  return *out_ptr == NULL ? 0 : 32;
+}
diff --git a/src/crypto/fipsmodule/CMakeLists.txt b/src/crypto/fipsmodule/CMakeLists.txt
index d1e2cb9..e978820 100644
--- a/src/crypto/fipsmodule/CMakeLists.txt
+++ b/src/crypto/fipsmodule/CMakeLists.txt
@@ -129,6 +129,10 @@
 perlasm(x86-mont.${ASM_EXT} bn/asm/x86-mont.pl)
 
 if(FIPS_DELOCATE)
+  if(FIPS_SHARED)
+    error("Can't set both delocate and shared mode for FIPS build")
+  endif()
+
   if(OPENSSL_NO_ASM)
     # If OPENSSL_NO_ASM was defined then ASM will not have been enabled, but in
     # FIPS mode we have to have it because the module build requires going via
@@ -189,12 +193,49 @@
 
     OBJECT
 
+    fips_shared_support.c
     is_fips.c
   )
 
   add_dependencies(fipsmodule global_target)
 
   set_target_properties(fipsmodule PROPERTIES LINKER_LANGUAGE C)
+elseif(FIPS_SHARED)
+  if(NOT BUILD_SHARED_LIBS)
+    error("FIPS_SHARED set but not BUILD_SHARED_LIBS")
+  endif()
+
+  add_library(
+    fipsmodule
+
+    OBJECT
+
+    fips_shared_support.c
+    is_fips.c
+  )
+
+  add_dependencies(fipsmodule global_target)
+
+  add_library(
+    bcm_library
+
+    STATIC
+
+    bcm.c
+
+    ${BCM_ASM_SOURCES}
+  )
+
+  add_dependencies(bcm_library global_target)
+
+  add_custom_command(
+    OUTPUT bcm.o
+    COMMAND ld -r -T ${CMAKE_CURRENT_SOURCE_DIR}/fips_shared.lds -o bcm.o --whole-archive $<TARGET_FILE:bcm_library>
+    DEPENDS bcm_library fips_shared.lds
+    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+  )
+
+  add_custom_target(bcm_o_target DEPENDS bcm.o)
 else()
   add_library(
     fipsmodule
@@ -202,6 +243,7 @@
     OBJECT
 
     bcm.c
+    fips_shared_support.c
     is_fips.c
 
     ${BCM_ASM_SOURCES}
diff --git a/src/crypto/fipsmodule/FIPS.md b/src/crypto/fipsmodule/FIPS.md
index a60e2bf..69719b6 100644
--- a/src/crypto/fipsmodule/FIPS.md
+++ b/src/crypto/fipsmodule/FIPS.md
@@ -13,9 +13,9 @@
 
 ## Running CAVP tests
 
-CAVP results are calculated by `fipstools/cavp`, but that binary is almost always run by `fipstools/run_cavp.go`. The latter knows the set of tests to be processed and the flags needed to configure `cavp` for each one. It must be run from the top of a CAVP directory and needs the following options:
+CAVP results are calculated by `util/fipstools/cavp`, but that binary is almost always run by `util/fipstools/run_cavp.go`. The latter knows the set of tests to be processed and the flags needed to configure `cavp` for each one. It must be run from the top of a CAVP directory and needs the following options:
 
-1. `-oracle-bin`: points to the location of `fipstools/cavp`
+1. `-oracle-bin`: points to the location of `util/fipstools/cavp`
 2. `-no-fax`: this is needed to suppress checking of the FAX files, which are only included in sample sets.
 
 ## Breaking power-on and continuous tests
@@ -62,9 +62,25 @@
 
 BoringCrypto is linked (often statically) into a large number of binaries. It would be a significant cost if each of these binaries had to be post-processed in order to calculate the known-good HMAC value. We would much prefer if the value could be calculated, once, when BoringCrypto itself is compiled.
 
-In order for the value to be calculated before the final link, there can be no relocations in the hashed code and data. This document describes how we build C and assembly code in order to produce an object file containing all the code and data for the FIPS module without that code having any relocations.
+In order for the value to be calculated before the final link, there can be no relocations in the hashed code and data. This document describes how we build C and assembly code in order to produce a binary file containing all the code and data for the FIPS module without that code having any relocations.
 
-First, all the C source files for the module are compiled as a single unit by compiling a single source file that `#include`s them all (this is `bcm.c`). The `-fPIC` flag is used to cause the compiler to use IP-relative addressing in many (but not all) cases. Also the `-S` flag is used to instruct the compiler to produce a textual assembly file rather than a binary object file.
+There are two build configurations supported: static and shared. The shared build produces `libcrypto.so`, which includes the FIPS module and is significantly more straightforward and so is described first:
+
+### Shared build
+
+First, all the C source files for the module are compiled as a single unit by compiling a single source file that `#include`s them all (this is `bcm.c`). This, along with some assembly sources, comprise the FIPS module.
+
+The object files resulting from compiling (or assembling) those files is linked in partial-linking mode with a linker script that causes the linker to insert symbols marking the beginning and end of the text and rodata sections. The linker script also discards other types of data sections to ensure that no unhashed data is used by the module.
+
+One source of such data are `rel.ro` sections, which contain data that includes function pointers. Since these function pointers are absolute, they are written by the dynamic linker at run-time and so we must eliminate them. The pattern that causes them is when we have a static `EVP_MD` or `EVP_CIPHER` object thus, inside the module, this pattern is changed to instead reserve space in the BSS for the object, and to add a `CRYPTO_once_t` to protect its initialisation.
+
+Once the partially-linked result is linked again, with other parts of libcrypto, to produce `libcrypto.so`, the contents of the module are fixed, as required. The module code uses the linker-added symbols to find the its code and data at run-time and hashes them upon initialisation. The result is compared against a value stored inside `libcrypto.so`, but outside of the module. That value will, initially, be incorrect, but `inject-hash.go` can inject the correct value.
+
+### Static build
+
+The static build cannot depend on the shared-object link to resolve relocations and thus must take another path.
+
+As with the shared build, all the C sources are build in a single compilation unit. The `-fPIC` flag is used to cause the compiler to use IP-relative addressing in many (but not all) cases. Also the `-S` flag is used to instruct the compiler to produce a textual assembly file rather than a binary object file.
 
 The textual assembly file is then processed by a script to merge in assembly implementations of some primitives and to eliminate the remaining sources of relocations.
 
@@ -80,9 +96,9 @@
 
 ##### Read-only data
 
-Normally read-only data is placed in a `.data` segment that doesn't get mapped into memory with execute permissions. However, the offset of the data segment from the text segment is another thing that isn't determined until the final link. In order to fix data offsets before the link, read-only data is simply placed in the module's `.text` segment. This might make building ROP chains easier for an attacker, but so it goes.
+Normally read-only data is placed in an `.rodata` segment that doesn't get mapped into memory with execute permissions. However, the offset of the data segment from the text segment is another thing that isn't determined until the final link. In order to fix data offsets before the link, read-only data is simply placed in the module's `.text` segment. This might make building ROP chains easier for an attacker, but so it goes.
 
-One special case is `rel.ro` data, which is data that contains function pointers. Since these function pointers are absolute, they are written by the dynamic linker at run-time and so we must eliminate them. The pattern that causes them is when we have a static `EVP_MD` or `EVP_CIPHER` object thus, inside the module, we'll change this pattern to instead to reserve space in the BSS for the object, and add a `CRYPTO_once_t` to protect its initialisation. The script will generate functions outside of the module that return pointers to these areas of memory—they effectively act like a special-purpose malloc calls that cannot fail.
+Data containing function pointers remains an issue. The source-code changes described above for the shared build apply here too, but no direct references to a BSS section are possible because the offset to that section is not known at compile time. Instead, the script generates functions outside of the module that return pointers to these areas of memory—they effectively act like special-purpose malloc calls that cannot fail.
 
 ##### Read-write data
 
@@ -92,7 +108,7 @@
 
 ##### Other transforms
 
-The script performs a number of other transformations which are worth noting but do not warrant their own sections:
+The script performs a number of other transformations which are worth noting but do not warrant their own discussions:
 
 1.  It duplicates each global symbol with a local symbol that has `_local_target` appended to the name. References to the global symbols are rewritten to use these duplicates instead. Otherwise, although the generated code uses IP-relative references, relocations are emitted for global symbols in case they are overridden by a different object file during the link.
 1.  Various sections, notably `.rodata`, are moved to the `.text` section, inside the module, so module code may reference it without relocations.
@@ -112,15 +128,15 @@
 
 (This is based on reading OpenSSL's [user guide](https://www.openssl.org/docs/fips/UserGuide-2.0.pdf) and inspecting the code of OpenSSL FIPS 2.0.12.)
 
-OpenSSL's solution to this problem is broadly similar but has a number of differences:
+OpenSSL's solution to this problem is very similar to our shared build, with just a few differences:
 
 1.  OpenSSL deals with run-time relocations by not hashing parts of the module's data.
-1.  OpenSSL uses `ld -r` (the partial linking mode) to merge a number of object files into their `fipscanister.o`. For BoringCrypto, we merge all the C source files by building a single C file that #includes all the others, and we merge the assembly sources by appending them to the assembly output from the C compiler.
-1.  OpenSSL depends on the link order and inserts two object files, `fips_start.o` and `fips_end.o`, in order to establish the `module_start` and `module_end` values. BoringCrypto adds labels at the correct places in the assembly.
+1.  OpenSSL uses `ld -r` (the partial linking mode) to merge a number of object files into their `fipscanister.o`. For BoringCrypto's static build, we merge all the C source files by building a single C file that #includes all the others, and we merge the assembly sources by appending them to the assembly output from the C compiler.
+1.  OpenSSL depends on the link order and inserts two object files, `fips_start.o` and `fips_end.o`, in order to establish the `module_start` and `module_end` values. BoringCrypto adds labels at the correct places in the assembly for the static build, or uses a linker script for the shared build.
 1.  OpenSSL calculates the hash after the final link and either injects it into the binary or recompiles with the value of the hash passed in as a #define. BoringCrypto calculates it prior to the final link and injects it into the object file.
 1.  OpenSSL references read-write data directly, since it can know the offsets to it. BoringCrypto indirects these loads and stores.
 1.  OpenSSL doesn't run the power-on test until `FIPS_module_mode_set` is called. BoringCrypto does it in a constructor function. Failure of the test is non-fatal in OpenSSL, BoringCrypto will crash.
-1.  Since the contents of OpenSSL's module change between compilation and use, OpenSSL generates `fipscanister.o.sha1` to check that the compiled object doesn't change before linking. Since BoringCrypto's module is fixed after compilation, the final integrity check is unbroken through the linking process.
+1.  Since the contents of OpenSSL's module change between compilation and use, OpenSSL generates `fipscanister.o.sha1` to check that the compiled object doesn't change before linking. Since BoringCrypto's module is fixed after compilation (in the static case), the final integrity check is unbroken through the linking process.
 
 Some of the similarities are worth noting:
 
diff --git a/src/crypto/fipsmodule/aes/aes_test.cc b/src/crypto/fipsmodule/aes/aes_test.cc
index 1f9a491..7fadb35 100644
--- a/src/crypto/fipsmodule/aes/aes_test.cc
+++ b/src/crypto/fipsmodule/aes/aes_test.cc
@@ -122,12 +122,40 @@
                                ciphertext.data(), ciphertext.size()));
 }
 
+static void TestKeyWrapWithPadding(FileTest *t) {
+  std::vector<uint8_t> key, plaintext, ciphertext;
+  ASSERT_TRUE(t->GetBytes(&key, "Key"));
+  ASSERT_TRUE(t->GetBytes(&plaintext, "Plaintext"));
+  ASSERT_TRUE(t->GetBytes(&ciphertext, "Ciphertext"));
+
+  // Test encryption.
+  AES_KEY aes_key;
+  ASSERT_EQ(0, AES_set_encrypt_key(key.data(), 8 * key.size(), &aes_key));
+  std::unique_ptr<uint8_t[]> buf(new uint8_t[plaintext.size() + 15]);
+  size_t len;
+  ASSERT_TRUE(AES_wrap_key_padded(&aes_key, buf.get(), &len,
+                                  plaintext.size() + 15, plaintext.data(),
+                                  plaintext.size()));
+  EXPECT_EQ(Bytes(ciphertext), Bytes(buf.get(), static_cast<size_t>(len)));
+
+  // Test decryption
+  ASSERT_EQ(0, AES_set_decrypt_key(key.data(), 8 * key.size(), &aes_key));
+  buf.reset(new uint8_t[ciphertext.size() - 8]);
+  ASSERT_TRUE(AES_unwrap_key_padded(&aes_key, buf.get(), &len,
+                                    ciphertext.size() - 8, ciphertext.data(),
+                                    ciphertext.size()));
+  ASSERT_EQ(len, plaintext.size());
+  EXPECT_EQ(Bytes(plaintext), Bytes(buf.get(), static_cast<size_t>(len)));
+}
+
 TEST(AESTest, TestVectors) {
   FileTestGTest("crypto/fipsmodule/aes/aes_tests.txt", [](FileTest *t) {
     if (t->GetParameter() == "Raw") {
       TestRaw(t);
     } else if (t->GetParameter() == "KeyWrap") {
       TestKeyWrap(t);
+    } else if (t->GetParameter() == "KeyWrapWithPadding") {
+      TestKeyWrapWithPadding(t);
     } else {
       ADD_FAILURE() << "Unknown mode " << t->GetParameter();
     }
@@ -172,6 +200,48 @@
   });
 }
 
+TEST(AESTest, WycheproofKeyWrapWithPadding) {
+  FileTestGTest("third_party/wycheproof_testvectors/kwp_test.txt",
+                [](FileTest *t) {
+    std::string key_size;
+    ASSERT_TRUE(t->GetInstruction(&key_size, "keySize"));
+    std::vector<uint8_t> ct, key, msg;
+    ASSERT_TRUE(t->GetBytes(&ct, "ct"));
+    ASSERT_TRUE(t->GetBytes(&key, "key"));
+    ASSERT_TRUE(t->GetBytes(&msg, "msg"));
+    ASSERT_EQ(static_cast<unsigned>(atoi(key_size.c_str())), key.size() * 8);
+    WycheproofResult result;
+    ASSERT_TRUE(GetWycheproofResult(t, &result));
+
+    // Wycheproof contains test vectors with empty messages that it believes
+    // should pass. However, both RFC 5649 and SP 800-38F section 5.3.1 say that
+    // the minimum length is one. Therefore we consider test cases with an empty
+    // message to be invalid.
+    if (result != WycheproofResult::kInvalid && !msg.empty()) {
+      AES_KEY aes;
+      ASSERT_EQ(0, AES_set_decrypt_key(key.data(), 8 * key.size(), &aes));
+      std::vector<uint8_t> out(ct.size() - 8);
+      size_t len;
+      ASSERT_TRUE(AES_unwrap_key_padded(&aes, out.data(), &len, ct.size() - 8,
+                                        ct.data(), ct.size()));
+      EXPECT_EQ(Bytes(msg), Bytes(out.data(), len));
+
+      out.resize(msg.size() + 15);
+      ASSERT_EQ(0, AES_set_encrypt_key(key.data(), 8 * key.size(), &aes));
+      ASSERT_TRUE(AES_wrap_key_padded(&aes, out.data(), &len, msg.size() + 15,
+                                      msg.data(), msg.size()));
+      EXPECT_EQ(Bytes(ct), Bytes(out.data(), len));
+    } else {
+      AES_KEY aes;
+      ASSERT_EQ(0, AES_set_decrypt_key(key.data(), 8 * key.size(), &aes));
+      std::vector<uint8_t> out(ct.size());
+      size_t len;
+      ASSERT_FALSE(AES_unwrap_key_padded(&aes, out.data(), &len, ct.size(),
+                                         ct.data(), ct.size()));
+    }
+  });
+}
+
 TEST(AESTest, WrapBadLengths) {
   uint8_t key[128/8] = {0};
   AES_KEY aes;
diff --git a/src/crypto/fipsmodule/aes/aes_tests.txt b/src/crypto/fipsmodule/aes/aes_tests.txt
index d4e4c61..efbe294 100644
--- a/src/crypto/fipsmodule/aes/aes_tests.txt
+++ b/src/crypto/fipsmodule/aes/aes_tests.txt
@@ -48,3 +48,16 @@
 Key = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
 Plaintext = 00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f
 Ciphertext = 28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21
+
+
+# Test vectors from https://tools.ietf.org/html/rfc5649#section-6
+
+Mode = KeyWrapWithPadding
+Key = 5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8
+Plaintext = c37b7e6492584340bed12207808941155068f738
+Ciphertext = 138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a
+
+Mode = KeyWrapWithPadding
+Key = 5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8
+Plaintext = 466f7250617369
+Ciphertext = afbeb0f07dfbf5419200f2ccb50bb24f
diff --git a/src/crypto/fipsmodule/aes/asm/aes-586.pl b/src/crypto/fipsmodule/aes/asm/aes-586.pl
index 25f1813..9b373de 100755
--- a/src/crypto/fipsmodule/aes/asm/aes-586.pl
+++ b/src/crypto/fipsmodule/aes/asm/aes-586.pl
@@ -2997,4 +2997,4 @@
 
 &asm_finish();
 
-close STDOUT;
+close STDOUT or die "error closing STDOUT";
diff --git a/src/crypto/fipsmodule/aes/asm/aes-armv4.pl b/src/crypto/fipsmodule/aes/asm/aes-armv4.pl
index f4ae922..9eebb22 100644
--- a/src/crypto/fipsmodule/aes/asm/aes-armv4.pl
+++ b/src/crypto/fipsmodule/aes/asm/aes-armv4.pl
@@ -1247,4 +1247,4 @@
 close SELF;
 
 print $code;
-close STDOUT;	# enforce flush
+close STDOUT or die "error closing STDOUT";	# enforce flush
diff --git a/src/crypto/fipsmodule/aes/asm/aes-x86_64.pl b/src/crypto/fipsmodule/aes/asm/aes-x86_64.pl
index ea8b9a4..5b95785 100755
--- a/src/crypto/fipsmodule/aes/asm/aes-x86_64.pl
+++ b/src/crypto/fipsmodule/aes/asm/aes-x86_64.pl
@@ -2906,4 +2906,4 @@
 
 print $code;
 
-close STDOUT;
+close STDOUT or die "error closing STDOUT";
diff --git a/src/crypto/fipsmodule/aes/asm/aesni-x86.pl b/src/crypto/fipsmodule/aes/asm/aesni-x86.pl
index fcb5b98..d57127a 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;
+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 b608425..15f6805 100644
--- a/src/crypto/fipsmodule/aes/asm/aesni-x86_64.pl
+++ b/src/crypto/fipsmodule/aes/asm/aesni-x86_64.pl
@@ -5108,4 +5108,4 @@
 
 print $code;
 
-close 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 201da1a..62d4842 100644
--- a/src/crypto/fipsmodule/aes/asm/aesp8-ppc.pl
+++ b/src/crypto/fipsmodule/aes/asm/aesp8-ppc.pl
@@ -1,5 +1,5 @@
 #! /usr/bin/env perl
-# Copyright 2014-2016 The OpenSSL Project Authors. All Rights Reserved.
+# Copyright 2014-2018 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
@@ -40,6 +40,8 @@
 #		CBC en-/decrypt	CTR	XTS
 # POWER8[le]	3.96/0.72	0.74	1.1
 # POWER8[be]	3.75/0.65	0.66	1.0
+# POWER9[le]	4.02/0.86	0.84	1.05
+# POWER9[be]	3.99/0.78	0.79	0.97
 
 $flavour = shift;
 
@@ -1827,7 +1829,7 @@
 	stvx_u		$out1,$x10,$out
 	stvx_u		$out2,$x20,$out
 	addi		$out,$out,0x30
-	b		Lcbc_dec8x_done
+	b		Lctr32_enc8x_done
 
 .align	5
 Lctr32_enc8x_two:
@@ -1839,7 +1841,7 @@
 	stvx_u		$out0,$x00,$out
 	stvx_u		$out1,$x10,$out
 	addi		$out,$out,0x20
-	b		Lcbc_dec8x_done
+	b		Lctr32_enc8x_done
 
 .align	5
 Lctr32_enc8x_one:
@@ -3802,4 +3804,4 @@
         print $_,"\n";
 }
 
-close 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 13f86a0..187c221 100644
--- a/src/crypto/fipsmodule/aes/asm/aesv8-armx.pl
+++ b/src/crypto/fipsmodule/aes/asm/aesv8-armx.pl
@@ -1018,4 +1018,4 @@
     }
 }
 
-close 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 d4db3b4..932b3b6 100644
--- a/src/crypto/fipsmodule/aes/asm/bsaes-armv7.pl
+++ b/src/crypto/fipsmodule/aes/asm/bsaes-armv7.pl
@@ -2431,4 +2431,4 @@
 
 print $code;
 
-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 5fa06d8..bae5e7e 100755
--- a/src/crypto/fipsmodule/aes/asm/vpaes-armv8.pl
+++ b/src/crypto/fipsmodule/aes/asm/vpaes-armv8.pl
@@ -1359,4 +1359,4 @@
 
 print $code;
 
-close 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 81e7e84..2b40362 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;
+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 9429344..45463e8 100644
--- a/src/crypto/fipsmodule/aes/asm/vpaes-x86_64.pl
+++ b/src/crypto/fipsmodule/aes/asm/vpaes-x86_64.pl
@@ -1550,4 +1550,4 @@
 
 print $code;
 
-close STDOUT;
+close STDOUT or die "error closing STDOUT";
diff --git a/src/crypto/fipsmodule/aes/key_wrap.c b/src/crypto/fipsmodule/aes/key_wrap.c
index a52c983..9a5b28d 100644
--- a/src/crypto/fipsmodule/aes/key_wrap.c
+++ b/src/crypto/fipsmodule/aes/key_wrap.c
@@ -48,6 +48,7 @@
 
 #include <openssl/aes.h>
 
+#include <assert.h>
 #include <limits.h>
 #include <string.h>
 
@@ -100,18 +101,17 @@
   return (int)in_len + 8;
 }
 
-int AES_unwrap_key(const AES_KEY *key, const uint8_t *iv, uint8_t *out,
-                   const uint8_t *in, size_t in_len) {
+// aes_unwrap_key_inner performs steps one and two from
+// https://tools.ietf.org/html/rfc3394#section-2.2.2
+static int aes_unwrap_key_inner(const AES_KEY *key, uint8_t *out,
+                                uint8_t out_iv[8], const uint8_t *in,
+                                size_t in_len) {
   // See RFC 3394, section 2.2.2. Additionally, note that section 2 requires the
   // plaintext be at least two 8-byte blocks, so the ciphertext must be at least
   // three blocks.
 
   if (in_len > INT_MAX || in_len < 24 || in_len % 8 != 0) {
-    return -1;
-  }
-
-  if (iv == NULL) {
-    iv = kDefaultIV;
+    return 0;
   }
 
   uint8_t A[AES_BLOCK_SIZE];
@@ -133,9 +133,104 @@
     }
   }
 
-  if (CRYPTO_memcmp(A, iv, 8) != 0) {
+  memcpy(out_iv, A, 8);
+  return 1;
+}
+
+int AES_unwrap_key(const AES_KEY *key, const uint8_t *iv, uint8_t *out,
+                   const uint8_t *in, size_t in_len) {
+  uint8_t calculated_iv[8];
+  if (!aes_unwrap_key_inner(key, out, calculated_iv, in, in_len)) {
+    return -1;
+  }
+
+  if (iv == NULL) {
+    iv = kDefaultIV;
+  }
+  if (CRYPTO_memcmp(calculated_iv, iv, 8) != 0) {
     return -1;
   }
 
   return (int)in_len - 8;
 }
+
+// kPaddingConstant is used in Key Wrap with Padding. See
+// https://tools.ietf.org/html/rfc5649#section-3
+static const uint8_t kPaddingConstant[4] = {0xa6, 0x59, 0x59, 0xa6};
+
+int AES_wrap_key_padded(const AES_KEY *key, uint8_t *out, size_t *out_len,
+                        size_t max_out, const uint8_t *in, size_t in_len) {
+  // See https://tools.ietf.org/html/rfc5649#section-4.1
+  const uint32_t in_len32_be = CRYPTO_bswap4(in_len);
+  const uint64_t in_len64 = in_len;
+  const size_t padded_len = (in_len + 7) & ~7;
+
+  *out_len = 0;
+  if (in_len == 0 || in_len64 > 0xffffffffu || in_len + 7 < in_len ||
+      padded_len + 8 < padded_len || max_out < padded_len + 8) {
+    return 0;
+  }
+
+  uint8_t block[AES_BLOCK_SIZE];
+  memcpy(block, kPaddingConstant, sizeof(kPaddingConstant));
+  memcpy(block + 4, &in_len32_be, sizeof(in_len32_be));
+
+  if (in_len <= 8) {
+    memset(block + 8, 0, 8);
+    memcpy(block + 8, in, in_len);
+    AES_encrypt(block, out, key);
+    *out_len = AES_BLOCK_SIZE;
+    return 1;
+  }
+
+  uint8_t *padded_in = OPENSSL_malloc(padded_len);
+  if (padded_in == NULL) {
+    return 0;
+  }
+  assert(padded_len >= 8);
+  memset(padded_in + padded_len - 8, 0, 8);
+  memcpy(padded_in, in, in_len);
+  const int ret = AES_wrap_key(key, block, out, padded_in, padded_len);
+  OPENSSL_free(padded_in);
+  if (ret < 0) {
+    return 0;
+  }
+  *out_len = ret;
+  return 1;
+}
+
+int AES_unwrap_key_padded(const AES_KEY *key, uint8_t *out, size_t *out_len,
+                          size_t max_out, const uint8_t *in, size_t in_len) {
+  *out_len = 0;
+  if (in_len < AES_BLOCK_SIZE || max_out < in_len - 8) {
+    return 0;
+  }
+
+  uint8_t iv[8];
+  if (in_len == AES_BLOCK_SIZE) {
+    uint8_t block[AES_BLOCK_SIZE];
+    AES_decrypt(in, block, key);
+    memcpy(iv, block, sizeof(iv));
+    memcpy(out, block + 8, 8);
+  } else if (!aes_unwrap_key_inner(key, out, iv, in, in_len)) {
+    return 0;
+  }
+  assert(in_len % 8 == 0);
+
+  crypto_word_t ok = constant_time_eq_int(
+      CRYPTO_memcmp(iv, kPaddingConstant, sizeof(kPaddingConstant)), 0);
+
+  uint32_t claimed_len32;
+  memcpy(&claimed_len32, iv + 4, sizeof(claimed_len32));
+  const size_t claimed_len = CRYPTO_bswap4(claimed_len32);
+  ok &= ~constant_time_is_zero_w(claimed_len);
+  ok &= constant_time_eq_w((claimed_len - 1) >> 3, (in_len - 9) >> 3);
+
+  // Check that padding bytes are all zero.
+  for (size_t i = in_len - 15; i < in_len - 8; i++) {
+    ok &= constant_time_is_zero_w(constant_time_ge_8(i, claimed_len) & out[i]);
+  }
+
+  *out_len = constant_time_select_w(ok, claimed_len, 0);
+  return ok & 1;
+}
diff --git a/src/crypto/fipsmodule/bcm.c b/src/crypto/fipsmodule/bcm.c
index e15ecb8..7666222 100644
--- a/src/crypto/fipsmodule/bcm.c
+++ b/src/crypto/fipsmodule/bcm.c
@@ -76,7 +76,6 @@
 #include "md4/md4.c"
 #include "md5/md5.c"
 #include "modes/cbc.c"
-#include "modes/ccm.c"
 #include "modes/cfb.c"
 #include "modes/ctr.c"
 #include "modes/gcm.c"
@@ -100,11 +99,16 @@
 #if defined(BORINGSSL_FIPS)
 
 #if !defined(OPENSSL_ASAN)
-// These symbols are filled in by delocate.go. They point to the start and end
-// of the module, and the location of the integrity hash, respectively.
+// These symbols are filled in by delocate.go (in static builds) or a linker
+// script (in shared builds). They point to the start and end of the module, and
+// the location of the integrity hash, respectively.
 extern const uint8_t BORINGSSL_bcm_text_start[];
 extern const uint8_t BORINGSSL_bcm_text_end[];
 extern const uint8_t BORINGSSL_bcm_text_hash[];
+#if defined(BORINGSSL_SHARED_LIBRARY)
+extern const uint8_t BORINGSSL_bcm_rodata_start[];
+extern const uint8_t BORINGSSL_bcm_rodata_end[];
+#endif
 #endif
 
 static void __attribute__((constructor))
@@ -116,17 +120,39 @@
   // .text section, which triggers the global-buffer overflow detection.
   const uint8_t *const start = BORINGSSL_bcm_text_start;
   const uint8_t *const end = BORINGSSL_bcm_text_end;
+#if defined(BORINGSSL_SHARED_LIBRARY)
+  const uint8_t *const rodata_start = BORINGSSL_bcm_rodata_start;
+  const uint8_t *const rodata_end = BORINGSSL_bcm_rodata_end;
+#endif
 
   static const uint8_t kHMACKey[64] = {0};
   uint8_t result[SHA512_DIGEST_LENGTH];
 
   unsigned result_len;
-  if (!HMAC(EVP_sha512(), kHMACKey, sizeof(kHMACKey), start, end - start,
-            result, &result_len) ||
+  HMAC_CTX hmac_ctx;
+  HMAC_CTX_init(&hmac_ctx);
+  if (!HMAC_Init_ex(&hmac_ctx, kHMACKey, sizeof(kHMACKey), EVP_sha512(),
+                    NULL /* no ENGINE */)) {
+    fprintf(stderr, "HMAC_Init_ex failed.\n");
+    goto err;
+  }
+#if defined(BORINGSSL_SHARED_LIBRARY)
+  uint64_t length = end - start;
+  HMAC_Update(&hmac_ctx, (const uint8_t *) &length, sizeof(length));
+  HMAC_Update(&hmac_ctx, start, length);
+
+  length = rodata_end - rodata_start;
+  HMAC_Update(&hmac_ctx, (const uint8_t *) &length, sizeof(length));
+  HMAC_Update(&hmac_ctx, rodata_start, length);
+#else
+  HMAC_Update(&hmac_ctx, start, end - start);
+#endif
+  if (!HMAC_Final(&hmac_ctx, result, &result_len) ||
       result_len != sizeof(result)) {
     fprintf(stderr, "HMAC failed.\n");
     goto err;
   }
+  HMAC_CTX_cleanup(&hmac_ctx);
 
   const uint8_t *expected = BORINGSSL_bcm_text_hash;
 
diff --git a/src/crypto/fipsmodule/bn/asm/armv4-mont.pl b/src/crypto/fipsmodule/bn/asm/armv4-mont.pl
index 2ee389e..f3aa4be 100644
--- a/src/crypto/fipsmodule/bn/asm/armv4-mont.pl
+++ b/src/crypto/fipsmodule/bn/asm/armv4-mont.pl
@@ -759,4 +759,4 @@
 	print $_,"\n";
 }
 
-close 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 aab9eaa..db2ba49 100644
--- a/src/crypto/fipsmodule/bn/asm/armv8-mont.pl
+++ b/src/crypto/fipsmodule/bn/asm/armv8-mont.pl
@@ -1507,4 +1507,4 @@
 
 print $code;
 
-close 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 16818d5..05ef28c 100644
--- a/src/crypto/fipsmodule/bn/asm/bn-586.pl
+++ b/src/crypto/fipsmodule/bn/asm/bn-586.pl
@@ -31,7 +31,7 @@
 
 &asm_finish();
 
-close 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 5eeeef9..abe328a 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;
+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 51feb69..a0da239 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;
+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 214f2b0..1f61ae5 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;
+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 3d98e72..0a9e4d1 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;
+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 abcfe6a..b2ff114 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;
+close STDOUT or die "error closing STDOUT";
diff --git a/src/crypto/fipsmodule/delocate.h b/src/crypto/fipsmodule/delocate.h
index 59effde..d6564e4 100644
--- a/src/crypto/fipsmodule/delocate.h
+++ b/src/crypto/fipsmodule/delocate.h
@@ -20,7 +20,8 @@
 #include "../internal.h"
 
 
-#if defined(BORINGSSL_FIPS) && !defined(OPENSSL_ASAN) && !defined(OPENSSL_MSAN)
+#if !defined(BORINGSSL_SHARED_LIBRARY) && defined(BORINGSSL_FIPS) && \
+    !defined(OPENSSL_ASAN) && !defined(OPENSSL_MSAN)
 #define DEFINE_BSS_GET(type, name)        \
   static type name __attribute__((used)); \
   type *name##_bss_get(void) __attribute__((const));
diff --git a/src/crypto/fipsmodule/digest/digest.c b/src/crypto/fipsmodule/digest/digest.c
index 6705867..68e81c4 100644
--- a/src/crypto/fipsmodule/digest/digest.c
+++ b/src/crypto/fipsmodule/digest/digest.c
@@ -115,6 +115,11 @@
 
 void EVP_MD_CTX_destroy(EVP_MD_CTX *ctx) { EVP_MD_CTX_free(ctx); }
 
+int EVP_DigestFinalXOF(EVP_MD_CTX *ctx, uint8_t *out, size_t len) {
+  OPENSSL_PUT_ERROR(DIGEST, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+  return 0;
+}
+
 int EVP_MD_CTX_copy_ex(EVP_MD_CTX *out, const EVP_MD_CTX *in) {
   // |in->digest| may be NULL if this is a signing |EVP_MD_CTX| for, e.g.,
   // Ed25519 which does not hash with |EVP_MD_CTX|.
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 5402885..994cb82 100755
--- a/src/crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl
+++ b/src/crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl
@@ -3112,17 +3112,24 @@
 
 	or	$acc5, $acc4			# see if result is zero
 	or	$acc0, $acc4
-	or	$acc1, $acc4
+	or	$acc1, $acc4			# !is_equal(U1, U2)
 
-	.byte	0x3e				# predict taken
-	jnz	.Ladd_proceed$x			# is_equal(U1,U2)?
 	movq	%xmm2, $acc0
 	movq	%xmm3, $acc1
-	test	$acc0, $acc0
-	jnz	.Ladd_proceed$x			# (in1infty || in2infty)?
-	test	$acc1, $acc1
-	jz	.Ladd_double$x			# is_equal(S1,S2)?
+	or	$acc0, $acc4
+	.byte	0x3e				# predict taken
+	jnz	.Ladd_proceed$x			# !is_equal(U1, U2) || in1infty || in2infty
 
+	# We now know A = B or A = -B and neither is infinity. Compare the
+	# y-coordinates via S1 and S2.
+	test	$acc1, $acc1
+	jz	.Ladd_double$x			# is_equal(S1, S2)
+
+	# A = -B, so the result is infinity.
+	#
+	# TODO(davidben): Does .Ladd_proceed handle this case? It seems to, in
+	# which case we should eliminate this special-case and simplify the
+	# timing analysis.
 	movq	%xmm0, $r_ptr			# restore $r_ptr
 	pxor	%xmm0, %xmm0
 	movdqu	%xmm0, 0x00($r_ptr)
@@ -4146,4 +4153,4 @@
 
 $code =~ s/\`([^\`]*)\`/eval $1/gem;
 print $code;
-close 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 0bb6547..c05abba 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;
+close STDOUT or die "error closing STDOUT";
diff --git a/src/crypto/fipsmodule/ec/ec.c b/src/crypto/fipsmodule/ec/ec.c
index a0305a6..158d66c 100644
--- a/src/crypto/fipsmodule/ec/ec.c
+++ b/src/crypto/fipsmodule/ec/ec.c
@@ -892,8 +892,6 @@
   }
 
   int ret = 0;
-  EC_SCALAR g_scalar_storage, p_scalar_storage;
-  EC_SCALAR *g_scalar_arg = NULL, *p_scalar_arg = NULL;
   BN_CTX *new_ctx = NULL;
   if (ctx == NULL) {
     new_ctx = BN_CTX_new();
@@ -903,35 +901,50 @@
     ctx = new_ctx;
   }
 
+  // If both |g_scalar| and |p_scalar| are non-NULL,
+  // |ec_point_mul_scalar_public| would share the doublings between the two
+  // products, which would be more efficient. However, we conservatively assume
+  // the caller needs a constant-time operation. (ECDSA verification does not
+  // use this function.)
+  //
+  // Previously, the low-level constant-time multiplication function aligned
+  // with this function's calling convention, but this was misleading. Curves
+  // which combined the two multiplications did not avoid the doubling case
+  // in the incomplete addition formula and were not constant-time.
+
   if (g_scalar != NULL) {
-    if (!arbitrary_bignum_to_scalar(group, &g_scalar_storage, g_scalar, ctx)) {
+    EC_SCALAR scalar;
+    if (!arbitrary_bignum_to_scalar(group, &scalar, g_scalar, ctx) ||
+        !ec_point_mul_scalar_base(group, &r->raw, &scalar)) {
       goto err;
     }
-    g_scalar_arg = &g_scalar_storage;
   }
 
   if (p_scalar != NULL) {
-    if (!arbitrary_bignum_to_scalar(group, &p_scalar_storage, p_scalar, ctx)) {
+    EC_SCALAR scalar;
+    EC_RAW_POINT tmp;
+    if (!arbitrary_bignum_to_scalar(group, &scalar, p_scalar, ctx) ||
+        !ec_point_mul_scalar(group, &tmp, &p->raw, &scalar)) {
       goto err;
     }
-    p_scalar_arg = &p_scalar_storage;
+    if (g_scalar == NULL) {
+      OPENSSL_memcpy(&r->raw, &tmp, sizeof(EC_RAW_POINT));
+    } else {
+      group->meth->add(group, &r->raw, &r->raw, &tmp);
+    }
   }
 
-  ret = ec_point_mul_scalar(group, &r->raw, g_scalar_arg,
-                            p == NULL ? NULL : &p->raw, p_scalar_arg);
+  ret = 1;
 
 err:
   BN_CTX_free(new_ctx);
-  OPENSSL_cleanse(&g_scalar_storage, sizeof(g_scalar_storage));
-  OPENSSL_cleanse(&p_scalar_storage, sizeof(p_scalar_storage));
   return ret;
 }
 
 int ec_point_mul_scalar_public(const EC_GROUP *group, EC_RAW_POINT *r,
                                const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
                                const EC_SCALAR *p_scalar) {
-  if ((g_scalar == NULL && p_scalar == NULL) ||
-      (p == NULL) != (p_scalar == NULL)) {
+  if (g_scalar == NULL || p_scalar == NULL || p == NULL) {
     OPENSSL_PUT_ERROR(EC, ERR_R_PASSED_NULL_PARAMETER);
     return 0;
   }
@@ -941,15 +954,24 @@
 }
 
 int ec_point_mul_scalar(const EC_GROUP *group, EC_RAW_POINT *r,
-                        const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
-                        const EC_SCALAR *p_scalar) {
-  if ((g_scalar == NULL && p_scalar == NULL) ||
-      (p == NULL) != (p_scalar == NULL)) {
+                        const EC_RAW_POINT *p, const EC_SCALAR *scalar) {
+  if (p == NULL || scalar == NULL) {
     OPENSSL_PUT_ERROR(EC, ERR_R_PASSED_NULL_PARAMETER);
     return 0;
   }
 
-  group->meth->mul(group, r, g_scalar, p, p_scalar);
+  group->meth->mul(group, r, p, scalar);
+  return 1;
+}
+
+int ec_point_mul_scalar_base(const EC_GROUP *group, EC_RAW_POINT *r,
+                             const EC_SCALAR *scalar) {
+  if (scalar == NULL) {
+    OPENSSL_PUT_ERROR(EC, ERR_R_PASSED_NULL_PARAMETER);
+    return 0;
+  }
+
+  group->meth->mul_base(group, r, scalar);
   return 1;
 }
 
diff --git a/src/crypto/fipsmodule/ec/ec_key.c b/src/crypto/fipsmodule/ec/ec_key.c
index 3ef17d9..3851c19 100644
--- a/src/crypto/fipsmodule/ec/ec_key.c
+++ b/src/crypto/fipsmodule/ec/ec_key.c
@@ -322,8 +322,8 @@
   if (eckey->priv_key != NULL) {
     point = EC_POINT_new(eckey->group);
     if (point == NULL ||
-        !ec_point_mul_scalar(eckey->group, &point->raw,
-                             &eckey->priv_key->scalar, NULL, NULL)) {
+        !ec_point_mul_scalar_base(eckey->group, &point->raw,
+                                  &eckey->priv_key->scalar)) {
       OPENSSL_PUT_ERROR(EC, ERR_R_EC_LIB);
       goto err;
     }
@@ -440,8 +440,7 @@
       // Generate the private key by testing candidates (FIPS 186-4 B.4.2).
       !ec_random_nonzero_scalar(key->group, &priv_key->scalar,
                                 kDefaultAdditionalData) ||
-      !ec_point_mul_scalar(key->group, &pub_key->raw, &priv_key->scalar, NULL,
-                           NULL)) {
+      !ec_point_mul_scalar_base(key->group, &pub_key->raw, &priv_key->scalar)) {
     EC_POINT_free(pub_key);
     ec_wrapped_scalar_free(priv_key);
     return 0;
diff --git a/src/crypto/fipsmodule/ec/ec_montgomery.c b/src/crypto/fipsmodule/ec/ec_montgomery.c
index caa1966..0cf1d91 100644
--- a/src/crypto/fipsmodule/ec/ec_montgomery.c
+++ b/src/crypto/fipsmodule/ec/ec_montgomery.c
@@ -282,7 +282,8 @@
   BN_ULONG yneq = ec_felem_non_zero_mask(group, &r);
 
   // This case will never occur in the constant-time |ec_GFp_mont_mul|.
-  if (!xneq && !yneq && z1nz && z2nz) {
+  BN_ULONG is_nontrivial_double = ~xneq & ~yneq & z1nz & z2nz;
+  if (is_nontrivial_double) {
     ec_GFp_mont_dbl(group, out, a);
     return;
   }
@@ -470,6 +471,7 @@
   out->add = ec_GFp_mont_add;
   out->dbl = ec_GFp_mont_dbl;
   out->mul = ec_GFp_mont_mul;
+  out->mul_base = ec_GFp_mont_mul_base;
   out->mul_public = ec_GFp_mont_mul_public;
   out->felem_mul = ec_GFp_mont_felem_mul;
   out->felem_sqr = ec_GFp_mont_felem_sqr;
diff --git a/src/crypto/fipsmodule/ec/ec_test.cc b/src/crypto/fipsmodule/ec/ec_test.cc
index 1219e2b..c0ad61f 100644
--- a/src/crypto/fipsmodule/ec/ec_test.cc
+++ b/src/crypto/fipsmodule/ec/ec_test.cc
@@ -764,7 +764,15 @@
   ASSERT_TRUE(BN_set_word(bn32.get(), 32));
   ASSERT_TRUE(EC_POINT_mul(group(), ret.get(), bn32.get(), p.get(), bn31.get(),
                            nullptr));
+  EXPECT_EQ(0, EC_POINT_cmp(group(), ret.get(), g, nullptr));
 
+  // Repeat the computation with |ec_point_mul_scalar_public|, which ties the
+  // additions together.
+  EC_SCALAR sc31, sc32;
+  ASSERT_TRUE(ec_bignum_to_scalar(group(), &sc31, bn31.get()));
+  ASSERT_TRUE(ec_bignum_to_scalar(group(), &sc32, bn32.get()));
+  ASSERT_TRUE(
+      ec_point_mul_scalar_public(group(), &ret->raw, &sc32, &p->raw, &sc31));
   EXPECT_EQ(0, EC_POINT_cmp(group(), ret.get(), g, nullptr));
 }
 
diff --git a/src/crypto/fipsmodule/ec/internal.h b/src/crypto/fipsmodule/ec/internal.h
index 05175a5..7934c3a 100644
--- a/src/crypto/fipsmodule/ec/internal.h
+++ b/src/crypto/fipsmodule/ec/internal.h
@@ -140,16 +140,15 @@
   // dbl sets |r| to |a| + |a|.
   void (*dbl)(const EC_GROUP *group, EC_RAW_POINT *r, const EC_RAW_POINT *a);
 
-  // Computes |r = g_scalar*generator + p_scalar*p| if |g_scalar| and |p_scalar|
-  // are both non-null. Computes |r = g_scalar*generator| if |p_scalar| is null.
-  // Computes |r = p_scalar*p| if g_scalar is null. At least one of |g_scalar|
-  // and |p_scalar| must be non-null, and |p| must be non-null if |p_scalar| is
-  // non-null.
-  void (*mul)(const EC_GROUP *group, EC_RAW_POINT *r, const EC_SCALAR *g_scalar,
-              const EC_RAW_POINT *p, const EC_SCALAR *p_scalar);
-  // mul_public performs the same computation as mul. It further assumes that
-  // the inputs are public so there is no concern about leaking their values
-  // through timing.
+  // mul sets |r| to |scalar|*|p|.
+  void (*mul)(const EC_GROUP *group, EC_RAW_POINT *r, const EC_RAW_POINT *p,
+              const EC_SCALAR *scalar);
+  // mul_base sets |r| to |scalar|*generator.
+  void (*mul_base)(const EC_GROUP *group, EC_RAW_POINT *r,
+                   const EC_SCALAR *scalar);
+  // mul_public sets |r| to |g_scalar|*generator + |p_scalar|*|p|. It assumes
+  // that the inputs are public so there is no concern about leaking their
+  // values through timing.
   void (*mul_public)(const EC_GROUP *group, EC_RAW_POINT *r,
                      const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
                      const EC_SCALAR *p_scalar);
@@ -325,13 +324,15 @@
 int ec_scalar_inv_montgomery_vartime(const EC_GROUP *group, EC_SCALAR *r,
                                      const EC_SCALAR *a);
 
-// ec_point_mul_scalar sets |r| to generator * |g_scalar| + |p| *
-// |p_scalar|. Unlike other functions which take |EC_SCALAR|, |g_scalar| and
-// |p_scalar| need not be fully reduced. They need only contain as many bits as
-// the order.
+// ec_point_mul_scalar sets |r| to |p| * |scalar|. Both inputs are considered
+// secret.
 int ec_point_mul_scalar(const EC_GROUP *group, EC_RAW_POINT *r,
-                        const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
-                        const EC_SCALAR *p_scalar);
+                        const EC_RAW_POINT *p, const EC_SCALAR *scalar);
+
+// ec_point_mul_scalar_base sets |r| to generator * |scalar|. |scalar| is
+// treated as secret.
+int ec_point_mul_scalar_base(const EC_GROUP *group, EC_RAW_POINT *r,
+                             const EC_SCALAR *scalar);
 
 // ec_point_mul_scalar_public performs the same computation as
 // ec_point_mul_scalar.  It further assumes that the inputs are public so
@@ -370,8 +371,9 @@
 int ec_field_element_to_scalar(const EC_GROUP *group, BIGNUM *r);
 
 void ec_GFp_mont_mul(const EC_GROUP *group, EC_RAW_POINT *r,
-                     const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
-                     const EC_SCALAR *p_scalar);
+                     const EC_RAW_POINT *p, const EC_SCALAR *scalar);
+void ec_GFp_mont_mul_base(const EC_GROUP *group, EC_RAW_POINT *r,
+                          const EC_SCALAR *scalar);
 
 // ec_compute_wNAF writes the modified width-(w+1) Non-Adjacent Form (wNAF) of
 // |scalar| to |out|. |out| must have room for |bits| + 1 elements, each of
diff --git a/src/crypto/fipsmodule/ec/p224-64.c b/src/crypto/fipsmodule/ec/p224-64.c
index 2749686..f8af39b 100644
--- a/src/crypto/fipsmodule/ec/p224-64.c
+++ b/src/crypto/fipsmodule/ec/p224-64.c
@@ -758,7 +758,9 @@
   z1_is_zero = p224_felem_is_zero(z1);
   z2_is_zero = p224_felem_is_zero(z2);
   // In affine coordinates, (X_1, Y_1) == (X_2, Y_2)
-  if (x_equal && y_equal && !z1_is_zero && !z2_is_zero) {
+  p224_limb is_nontrivial_double =
+      x_equal & y_equal & (1 - z1_is_zero) & (1 - z2_is_zero);
+  if (is_nontrivial_double) {
     p224_point_double(x3, y3, z3, x1, y1, z1);
     return;
   }
@@ -871,95 +873,6 @@
   return (in[i >> 3] >> (i & 7)) & 1;
 }
 
-// Interleaved point multiplication using precomputed point multiples:
-// The small point multiples 0*P, 1*P, ..., 16*P are in p_pre_comp, the scalars
-// in p_scalar, if non-NULL. If g_scalar is non-NULL, we also add this multiple
-// of the generator, using certain (large) precomputed multiples in
-// g_p224_pre_comp. Output point (X, Y, Z) is stored in x_out, y_out, z_out
-static void p224_batch_mul(p224_felem x_out, p224_felem y_out, p224_felem z_out,
-                           const uint8_t *p_scalar, const uint8_t *g_scalar,
-                           const p224_felem p_pre_comp[17][3]) {
-  p224_felem nq[3], tmp[4];
-  uint64_t bits;
-  uint8_t sign, digit;
-
-  // set nq to the point at infinity
-  OPENSSL_memset(nq, 0, 3 * sizeof(p224_felem));
-
-  // Loop over both scalars msb-to-lsb, interleaving additions of multiples of
-  // the generator (two in each of the last 28 rounds) and additions of p (every
-  // 5th round).
-  int skip = 1;  // save two point operations in the first round
-  size_t i = p_scalar != NULL ? 220 : 27;
-  for (;;) {
-    // double
-    if (!skip) {
-      p224_point_double(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2]);
-    }
-
-    // add multiples of the generator
-    if (g_scalar != NULL && i <= 27) {
-      // first, look 28 bits upwards
-      bits = p224_get_bit(g_scalar, i + 196) << 3;
-      bits |= p224_get_bit(g_scalar, i + 140) << 2;
-      bits |= p224_get_bit(g_scalar, i + 84) << 1;
-      bits |= p224_get_bit(g_scalar, i + 28);
-      // select the point to add, in constant time
-      p224_select_point(bits, 16, g_p224_pre_comp[1], tmp);
-
-      if (!skip) {
-        p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 1 /* mixed */,
-                       tmp[0], tmp[1], tmp[2]);
-      } else {
-        OPENSSL_memcpy(nq, tmp, 3 * sizeof(p224_felem));
-        skip = 0;
-      }
-
-      // second, look at the current position
-      bits = p224_get_bit(g_scalar, i + 168) << 3;
-      bits |= p224_get_bit(g_scalar, i + 112) << 2;
-      bits |= p224_get_bit(g_scalar, i + 56) << 1;
-      bits |= p224_get_bit(g_scalar, i);
-      // select the point to add, in constant time
-      p224_select_point(bits, 16, g_p224_pre_comp[0], tmp);
-      p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 1 /* mixed */,
-                     tmp[0], tmp[1], tmp[2]);
-    }
-
-    // do other additions every 5 doublings
-    if (p_scalar != NULL && i % 5 == 0) {
-      bits = p224_get_bit(p_scalar, i + 4) << 5;
-      bits |= p224_get_bit(p_scalar, i + 3) << 4;
-      bits |= p224_get_bit(p_scalar, i + 2) << 3;
-      bits |= p224_get_bit(p_scalar, i + 1) << 2;
-      bits |= p224_get_bit(p_scalar, i) << 1;
-      bits |= p224_get_bit(p_scalar, i - 1);
-      ec_GFp_nistp_recode_scalar_bits(&sign, &digit, bits);
-
-      // select the point to add or subtract
-      p224_select_point(digit, 17, p_pre_comp, tmp);
-      p224_felem_neg(tmp[3], tmp[1]);  // (X, -Y, Z) is the negative point
-      p224_copy_conditional(tmp[1], tmp[3], sign);
-
-      if (!skip) {
-        p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 0 /* mixed */,
-                       tmp[0], tmp[1], tmp[2]);
-      } else {
-        OPENSSL_memcpy(nq, tmp, 3 * sizeof(p224_felem));
-        skip = 0;
-      }
-    }
-
-    if (i == 0) {
-      break;
-    }
-    --i;
-  }
-  p224_felem_assign(x_out, nq[0]);
-  p224_felem_assign(y_out, nq[1]);
-  p224_felem_assign(z_out, nq[2]);
-}
-
 // Takes the Jacobian coordinates (X, Y, Z) of a point and returns
 // (X', Y') = (X/Z^2, Y/Z^3)
 static int ec_GFp_nistp224_point_get_affine_coordinates(
@@ -1027,49 +940,197 @@
   p224_felem_to_generic(&r->Z, z);
 }
 
-static void ec_GFp_nistp224_points_mul(const EC_GROUP *group, EC_RAW_POINT *r,
-                                       const EC_SCALAR *g_scalar,
-                                       const EC_RAW_POINT *p,
-                                       const EC_SCALAR *p_scalar) {
+static void ec_GFp_nistp224_make_precomp(p224_felem out[17][3],
+                                         const EC_RAW_POINT *p) {
+  OPENSSL_memset(out[0], 0, sizeof(p224_felem) * 3);
+
+  p224_generic_to_felem(out[1][0], &p->X);
+  p224_generic_to_felem(out[1][1], &p->Y);
+  p224_generic_to_felem(out[1][2], &p->Z);
+
+  for (size_t j = 2; j <= 16; ++j) {
+    if (j & 1) {
+      p224_point_add(out[j][0], out[j][1], out[j][2], out[1][0], out[1][1],
+                     out[1][2], 0, out[j - 1][0], out[j - 1][1], out[j - 1][2]);
+    } else {
+      p224_point_double(out[j][0], out[j][1], out[j][2], out[j / 2][0],
+                        out[j / 2][1], out[j / 2][2]);
+    }
+  }
+}
+
+static void ec_GFp_nistp224_point_mul(const EC_GROUP *group, EC_RAW_POINT *r,
+                                      const EC_RAW_POINT *p,
+                                      const EC_SCALAR *scalar) {
   p224_felem p_pre_comp[17][3];
-  p224_felem x_out, y_out, z_out;
+  ec_GFp_nistp224_make_precomp(p_pre_comp, p);
 
-  if (p != NULL && p_scalar != NULL) {
-    // We treat NULL scalars as 0, and NULL points as points at infinity, i.e.,
-    // they contribute nothing to the linear combination.
-    OPENSSL_memset(&p_pre_comp, 0, sizeof(p_pre_comp));
-    // precompute multiples
-    p224_generic_to_felem(x_out, &p->X);
-    p224_generic_to_felem(y_out, &p->Y);
-    p224_generic_to_felem(z_out, &p->Z);
+  // Set nq to the point at infinity.
+  p224_felem nq[3], tmp[4];
+  OPENSSL_memset(nq, 0, 3 * sizeof(p224_felem));
 
-    p224_felem_assign(p_pre_comp[1][0], x_out);
-    p224_felem_assign(p_pre_comp[1][1], y_out);
-    p224_felem_assign(p_pre_comp[1][2], z_out);
+  int skip = 1;  // Save two point operations in the first round.
+  for (size_t i = 220; i < 221; i--) {
+    if (!skip) {
+      p224_point_double(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2]);
+    }
 
-    for (size_t j = 2; j <= 16; ++j) {
-      if (j & 1) {
-        p224_point_add(p_pre_comp[j][0], p_pre_comp[j][1], p_pre_comp[j][2],
-                       p_pre_comp[1][0], p_pre_comp[1][1], p_pre_comp[1][2], 0,
-                       p_pre_comp[j - 1][0], p_pre_comp[j - 1][1],
-                       p_pre_comp[j - 1][2]);
+    // Add every 5 doublings.
+    if (i % 5 == 0) {
+      uint64_t bits = p224_get_bit(scalar->bytes, i + 4) << 5;
+      bits |= p224_get_bit(scalar->bytes, i + 3) << 4;
+      bits |= p224_get_bit(scalar->bytes, i + 2) << 3;
+      bits |= p224_get_bit(scalar->bytes, i + 1) << 2;
+      bits |= p224_get_bit(scalar->bytes, i) << 1;
+      bits |= p224_get_bit(scalar->bytes, i - 1);
+      uint8_t sign, digit;
+      ec_GFp_nistp_recode_scalar_bits(&sign, &digit, bits);
+
+      // Select the point to add or subtract.
+      p224_select_point(digit, 17, (const p224_felem(*)[3])p_pre_comp, tmp);
+      p224_felem_neg(tmp[3], tmp[1]);  // (X, -Y, Z) is the negative point
+      p224_copy_conditional(tmp[1], tmp[3], sign);
+
+      if (!skip) {
+        p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 0 /* mixed */,
+                       tmp[0], tmp[1], tmp[2]);
       } else {
-        p224_point_double(p_pre_comp[j][0], p_pre_comp[j][1], p_pre_comp[j][2],
-                          p_pre_comp[j / 2][0], p_pre_comp[j / 2][1],
-                          p_pre_comp[j / 2][2]);
+        OPENSSL_memcpy(nq, tmp, 3 * sizeof(p224_felem));
+        skip = 0;
       }
     }
   }
 
-  p224_batch_mul(x_out, y_out, z_out,
-                 (p != NULL && p_scalar != NULL) ? p_scalar->bytes : NULL,
-                 g_scalar != NULL ? g_scalar->bytes : NULL,
-                 (const p224_felem(*)[3])p_pre_comp);
+  // Reduce the output to its unique minimal representation.
+  p224_felem_to_generic(&r->X, nq[0]);
+  p224_felem_to_generic(&r->Y, nq[1]);
+  p224_felem_to_generic(&r->Z, nq[2]);
+}
 
-  // reduce the output to its unique minimal representation
-  p224_felem_to_generic(&r->X, x_out);
-  p224_felem_to_generic(&r->Y, y_out);
-  p224_felem_to_generic(&r->Z, z_out);
+static void ec_GFp_nistp224_point_mul_base(const EC_GROUP *group,
+                                           EC_RAW_POINT *r,
+                                           const EC_SCALAR *scalar) {
+  // Set nq to the point at infinity.
+  p224_felem nq[3], tmp[3];
+  OPENSSL_memset(nq, 0, 3 * sizeof(p224_felem));
+
+  int skip = 1;  // Save two point operations in the first round.
+  for (size_t i = 27; i < 28; i--) {
+    // double
+    if (!skip) {
+      p224_point_double(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2]);
+    }
+
+    // First, look 28 bits upwards.
+    uint64_t bits = p224_get_bit(scalar->bytes, i + 196) << 3;
+    bits |= p224_get_bit(scalar->bytes, i + 140) << 2;
+    bits |= p224_get_bit(scalar->bytes, i + 84) << 1;
+    bits |= p224_get_bit(scalar->bytes, i + 28);
+    // Select the point to add, in constant time.
+    p224_select_point(bits, 16, g_p224_pre_comp[1], tmp);
+
+    if (!skip) {
+      p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 1 /* mixed */,
+                     tmp[0], tmp[1], tmp[2]);
+    } else {
+      OPENSSL_memcpy(nq, tmp, 3 * sizeof(p224_felem));
+      skip = 0;
+    }
+
+    // Second, look at the current position/
+    bits = p224_get_bit(scalar->bytes, i + 168) << 3;
+    bits |= p224_get_bit(scalar->bytes, i + 112) << 2;
+    bits |= p224_get_bit(scalar->bytes, i + 56) << 1;
+    bits |= p224_get_bit(scalar->bytes, i);
+    // Select the point to add, in constant time.
+    p224_select_point(bits, 16, g_p224_pre_comp[0], tmp);
+    p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 1 /* mixed */,
+                   tmp[0], tmp[1], tmp[2]);
+  }
+
+  // Reduce the output to its unique minimal representation.
+  p224_felem_to_generic(&r->X, nq[0]);
+  p224_felem_to_generic(&r->Y, nq[1]);
+  p224_felem_to_generic(&r->Z, nq[2]);
+}
+
+static void ec_GFp_nistp224_point_mul_public(const EC_GROUP *group,
+                                             EC_RAW_POINT *r,
+                                             const EC_SCALAR *g_scalar,
+                                             const EC_RAW_POINT *p,
+                                             const EC_SCALAR *p_scalar) {
+  // TODO(davidben): If P-224 ECDSA verify performance ever matters, using
+  // |ec_compute_wNAF| for |p_scalar| would likely be an easy improvement.
+  p224_felem p_pre_comp[17][3];
+  ec_GFp_nistp224_make_precomp(p_pre_comp, p);
+
+  // Set nq to the point at infinity.
+  p224_felem nq[3], tmp[3];
+  OPENSSL_memset(nq, 0, 3 * sizeof(p224_felem));
+
+  // Loop over both scalars msb-to-lsb, interleaving additions of multiples of
+  // the generator (two in each of the last 28 rounds) and additions of p (every
+  // 5th round).
+  int skip = 1;  // Save two point operations in the first round.
+  for (size_t i = 220; i < 221; i--) {
+    if (!skip) {
+      p224_point_double(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2]);
+    }
+
+    // Add multiples of the generator.
+    if (i <= 27) {
+      // First, look 28 bits upwards.
+      uint64_t bits = p224_get_bit(g_scalar->bytes, i + 196) << 3;
+      bits |= p224_get_bit(g_scalar->bytes, i + 140) << 2;
+      bits |= p224_get_bit(g_scalar->bytes, i + 84) << 1;
+      bits |= p224_get_bit(g_scalar->bytes, i + 28);
+
+      p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 1 /* mixed */,
+                     g_p224_pre_comp[1][bits][0], g_p224_pre_comp[1][bits][1],
+                     g_p224_pre_comp[1][bits][2]);
+      assert(!skip);
+
+      // Second, look at the current position.
+      bits = p224_get_bit(g_scalar->bytes, i + 168) << 3;
+      bits |= p224_get_bit(g_scalar->bytes, i + 112) << 2;
+      bits |= p224_get_bit(g_scalar->bytes, i + 56) << 1;
+      bits |= p224_get_bit(g_scalar->bytes, i);
+      p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 1 /* mixed */,
+                     g_p224_pre_comp[0][bits][0], g_p224_pre_comp[0][bits][1],
+                     g_p224_pre_comp[0][bits][2]);
+    }
+
+    // Incorporate |p_scalar| every 5 doublings.
+    if (i % 5 == 0) {
+      uint64_t bits = p224_get_bit(p_scalar->bytes, i + 4) << 5;
+      bits |= p224_get_bit(p_scalar->bytes, i + 3) << 4;
+      bits |= p224_get_bit(p_scalar->bytes, i + 2) << 3;
+      bits |= p224_get_bit(p_scalar->bytes, i + 1) << 2;
+      bits |= p224_get_bit(p_scalar->bytes, i) << 1;
+      bits |= p224_get_bit(p_scalar->bytes, i - 1);
+      uint8_t sign, digit;
+      ec_GFp_nistp_recode_scalar_bits(&sign, &digit, bits);
+
+      // Select the point to add or subtract.
+      OPENSSL_memcpy(tmp, p_pre_comp[digit], 3 * sizeof(p224_felem));
+      if (sign) {
+        p224_felem_neg(tmp[1], tmp[1]);  // (X, -Y, Z) is the negative point
+      }
+
+      if (!skip) {
+        p224_point_add(nq[0], nq[1], nq[2], nq[0], nq[1], nq[2], 0 /* mixed */,
+                       tmp[0], tmp[1], tmp[2]);
+      } else {
+        OPENSSL_memcpy(nq, tmp, 3 * sizeof(p224_felem));
+        skip = 0;
+      }
+    }
+  }
+
+  // Reduce the output to its unique minimal representation.
+  p224_felem_to_generic(&r->X, nq[0]);
+  p224_felem_to_generic(&r->Y, nq[1]);
+  p224_felem_to_generic(&r->Z, nq[2]);
 }
 
 static void ec_GFp_nistp224_felem_mul(const EC_GROUP *group, EC_FELEM *r,
@@ -1111,8 +1172,9 @@
       ec_GFp_nistp224_point_get_affine_coordinates;
   out->add = ec_GFp_nistp224_add;
   out->dbl = ec_GFp_nistp224_dbl;
-  out->mul = ec_GFp_nistp224_points_mul;
-  out->mul_public = ec_GFp_nistp224_points_mul;
+  out->mul = ec_GFp_nistp224_point_mul;
+  out->mul_base = ec_GFp_nistp224_point_mul_base;
+  out->mul_public = ec_GFp_nistp224_point_mul_public;
   out->felem_mul = ec_GFp_nistp224_felem_mul;
   out->felem_sqr = ec_GFp_nistp224_felem_sqr;
   out->bignum_to_felem = ec_GFp_nistp224_bignum_to_felem;
diff --git a/src/crypto/fipsmodule/ec/p256-x86_64.c b/src/crypto/fipsmodule/ec/p256-x86_64.c
index dd8108d..b4af544 100644
--- a/src/crypto/fipsmodule/ec/p256-x86_64.c
+++ b/src/crypto/fipsmodule/ec/p256-x86_64.c
@@ -48,7 +48,8 @@
 // Precomputed tables for the default generator
 #include "p256-x86_64-table.h"
 
-// Recode window to a signed digit, see util-64.c for details
+// Recode window to a signed digit, see |ec_GFp_nistp_recode_scalar_bits| in
+// util.c for details
 static unsigned booth_recode_w5(unsigned in) {
   unsigned s, d;
 
@@ -316,74 +317,57 @@
   return booth_recode_w7(wvalue);
 }
 
-static void mul_p_add_and_store(const EC_GROUP *group, EC_RAW_POINT *r,
-                                const EC_SCALAR *g_scalar,
-                                const EC_RAW_POINT *p_,
-                                const EC_SCALAR *p_scalar,
-                                p256_point_union_t *t, p256_point_union_t *p) {
-  const int p_is_infinity = g_scalar == NULL;
-  if (p_scalar != NULL) {
-    P256_POINT *out = &t->p;
-    if (p_is_infinity) {
-      out = &p->p;
-    }
+static void ecp_nistz256_point_mul(const EC_GROUP *group, EC_RAW_POINT *r,
+                                   const EC_RAW_POINT *p,
+                                   const EC_SCALAR *scalar) {
+  alignas(32) P256_POINT out;
+  ecp_nistz256_windowed_mul(group, &out, p, scalar);
 
-    ecp_nistz256_windowed_mul(group, out, p_, p_scalar);
-    if (!p_is_infinity) {
-      ecp_nistz256_point_add(&p->p, &p->p, out);
-    }
+  assert(group->field.width == P256_LIMBS);
+  OPENSSL_memcpy(r->X.words, out.X, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Y.words, out.Y, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Z.words, out.Z, P256_LIMBS * sizeof(BN_ULONG));
+}
+
+static void ecp_nistz256_point_mul_base(const EC_GROUP *group, EC_RAW_POINT *r,
+                                        const EC_SCALAR *scalar) {
+  alignas(32) p256_point_union_t t, p;
+
+  uint8_t p_str[33];
+  OPENSSL_memcpy(p_str, scalar->bytes, 32);
+  p_str[32] = 0;
+
+  // First window
+  unsigned index = 0;
+  unsigned wvalue = calc_first_wvalue(&index, p_str);
+
+  ecp_nistz256_select_w7(&p.a, ecp_nistz256_precomputed[0], wvalue >> 1);
+  ecp_nistz256_neg(p.p.Z, p.p.Y);
+  copy_conditional(p.p.Y, p.p.Z, wvalue & 1);
+
+  // Convert |p| from affine to Jacobian coordinates. We set Z to zero if |p|
+  // is infinity and |ONE| otherwise. |p| was computed from the table, so it
+  // is infinity iff |wvalue >> 1| is zero.
+  OPENSSL_memset(p.p.Z, 0, sizeof(p.p.Z));
+  copy_conditional(p.p.Z, ONE, is_not_zero(wvalue >> 1));
+
+  for (int i = 1; i < 37; i++) {
+    wvalue = calc_wvalue(&index, p_str);
+
+    ecp_nistz256_select_w7(&t.a, ecp_nistz256_precomputed[i], wvalue >> 1);
+
+    ecp_nistz256_neg(t.p.Z, t.a.Y);
+    copy_conditional(t.a.Y, t.p.Z, wvalue & 1);
+
+    // Note |ecp_nistz256_point_add_affine| does not work if |p.p| and |t.a|
+    // are the same non-infinity point.
+    ecp_nistz256_point_add_affine(&p.p, &p.p, &t.a);
   }
 
   assert(group->field.width == P256_LIMBS);
-  OPENSSL_memcpy(r->X.words, p->p.X, P256_LIMBS * sizeof(BN_ULONG));
-  OPENSSL_memcpy(r->Y.words, p->p.Y, P256_LIMBS * sizeof(BN_ULONG));
-  OPENSSL_memcpy(r->Z.words, p->p.Z, P256_LIMBS * sizeof(BN_ULONG));
-}
-
-static void ecp_nistz256_points_mul(const EC_GROUP *group, EC_RAW_POINT *r,
-                                    const EC_SCALAR *g_scalar,
-                                    const EC_RAW_POINT *p_,
-                                    const EC_SCALAR *p_scalar) {
-  assert((p_ != NULL) == (p_scalar != NULL));
-
-  alignas(32) p256_point_union_t t, p;
-
-  if (g_scalar != NULL) {
-    uint8_t p_str[33];
-    OPENSSL_memcpy(p_str, g_scalar->bytes, 32);
-    p_str[32] = 0;
-
-    // First window
-    unsigned index = 0;
-    unsigned wvalue = calc_first_wvalue(&index, p_str);
-
-    ecp_nistz256_select_w7(&p.a, ecp_nistz256_precomputed[0], wvalue >> 1);
-
-    ecp_nistz256_neg(p.p.Z, p.p.Y);
-    copy_conditional(p.p.Y, p.p.Z, wvalue & 1);
-
-    // Convert |p| from affine to Jacobian coordinates. We set Z to zero if |p|
-    // is infinity and |ONE| otherwise. |p| was computed from the table, so it
-    // is infinity iff |wvalue >> 1| is zero.
-    OPENSSL_memset(p.p.Z, 0, sizeof(p.p.Z));
-    copy_conditional(p.p.Z, ONE, is_not_zero(wvalue >> 1));
-
-    for (int i = 1; i < 37; i++) {
-      wvalue = calc_wvalue(&index, p_str);
-
-      ecp_nistz256_select_w7(&t.a, ecp_nistz256_precomputed[i], wvalue >> 1);
-
-      ecp_nistz256_neg(t.p.Z, t.a.Y);
-      copy_conditional(t.a.Y, t.p.Z, wvalue & 1);
-
-      // Note |ecp_nistz256_point_add_affine| does not work if |p.p| and |t.a|
-      // are the same non-infinity point, so it is important that we compute the
-      // |g_scalar| term before the |p_scalar| term.
-      ecp_nistz256_point_add_affine(&p.p, &p.p, &t.a);
-    }
-  }
-
-  mul_p_add_and_store(group, r, g_scalar, p_, p_scalar, &t, &p);
+  OPENSSL_memcpy(r->X.words, p.p.X, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Y.words, p.p.Y, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Z.words, p.p.Z, P256_LIMBS * sizeof(BN_ULONG));
 }
 
 static void ecp_nistz256_points_mul_public(const EC_GROUP *group,
@@ -438,7 +422,13 @@
     ecp_nistz256_point_add_affine(&p.p, &p.p, &t.a);
   }
 
-  mul_p_add_and_store(group, r, g_scalar, p_, p_scalar, &t, &p);
+  ecp_nistz256_windowed_mul(group, &t.p, p_, p_scalar);
+  ecp_nistz256_point_add(&p.p, &p.p, &t.p);
+
+  assert(group->field.width == P256_LIMBS);
+  OPENSSL_memcpy(r->X.words, p.p.X, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Y.words, p.p.Y, P256_LIMBS * sizeof(BN_ULONG));
+  OPENSSL_memcpy(r->Z.words, p.p.Z, P256_LIMBS * sizeof(BN_ULONG));
 }
 
 static int ecp_nistz256_get_affine(const EC_GROUP *group,
@@ -645,7 +635,8 @@
   out->point_get_affine_coordinates = ecp_nistz256_get_affine;
   out->add = ecp_nistz256_add;
   out->dbl = ecp_nistz256_dbl;
-  out->mul = ecp_nistz256_points_mul;
+  out->mul = ecp_nistz256_point_mul;
+  out->mul_base = ecp_nistz256_point_mul_base;
   out->mul_public = ecp_nistz256_points_mul_public;
   out->felem_mul = ec_GFp_mont_felem_mul;
   out->felem_sqr = ec_GFp_mont_felem_sqr;
diff --git a/src/crypto/fipsmodule/ec/simple_mul.c b/src/crypto/fipsmodule/ec/simple_mul.c
index e05f491..4ed6c48 100644
--- a/src/crypto/fipsmodule/ec/simple_mul.c
+++ b/src/crypto/fipsmodule/ec/simple_mul.c
@@ -21,9 +21,8 @@
 #include "../../internal.h"
 
 
-static void ec_GFp_mont_mul_single(const EC_GROUP *group, EC_RAW_POINT *r,
-                                   const EC_RAW_POINT *p,
-                                   const EC_SCALAR *scalar) {
+void ec_GFp_mont_mul(const EC_GROUP *group, EC_RAW_POINT *r,
+                     const EC_RAW_POINT *p, const EC_SCALAR *scalar) {
   // This is a generic implementation for uncommon curves that not do not
   // warrant a tuned one. It uses unsigned digits so that the doubling case in
   // |ec_GFp_mont_add| is always unreachable, erring on safety and simplicity.
@@ -79,21 +78,7 @@
   }
 }
 
-void ec_GFp_mont_mul(const EC_GROUP *group, EC_RAW_POINT *r,
-                     const EC_SCALAR *g_scalar, const EC_RAW_POINT *p,
-                     const EC_SCALAR *p_scalar) {
-  assert(g_scalar != NULL || p_scalar != NULL);
-  if (p_scalar == NULL) {
-    ec_GFp_mont_mul_single(group, r, &group->generator->raw, g_scalar);
-  } else if (g_scalar == NULL) {
-    ec_GFp_mont_mul_single(group, r, p, p_scalar);
-  } else {
-    // Support constant-time two-point multiplication for compatibility.  This
-    // does not actually come up in keygen, ECDH, or ECDSA, so we implement it
-    // the naive way.
-    ec_GFp_mont_mul_single(group, r, &group->generator->raw, g_scalar);
-    EC_RAW_POINT tmp;
-    ec_GFp_mont_mul_single(group, &tmp, p, p_scalar);
-    ec_GFp_mont_add(group, r, r, &tmp);
-  }
+void ec_GFp_mont_mul_base(const EC_GROUP *group, EC_RAW_POINT *r,
+                          const EC_SCALAR *scalar) {
+  ec_GFp_mont_mul(group, r, &group->generator->raw, scalar);
 }
diff --git a/src/crypto/fipsmodule/ec/util.c b/src/crypto/fipsmodule/ec/util.c
index 7303a15..4f39f18 100644
--- a/src/crypto/fipsmodule/ec/util.c
+++ b/src/crypto/fipsmodule/ec/util.c
@@ -18,6 +18,7 @@
 
 #include "internal.h"
 
+
 // This function looks at 5+1 scalar bits (5 current, 1 adjacent less
 // significant bit), and recodes them into a signed digit for use in fast point
 // multiplication: the use of signed rather than unsigned digits means that
@@ -43,13 +44,13 @@
 //     of a nonnegative integer (b_k in {0, 1}), rewrite it in digits 0, 1, -1
 //     by using bit-wise subtraction as follows:
 //
-//        b_k b_(k-1)  ...  b_2  b_1  b_0
-//      -     b_k      ...  b_3  b_2  b_1  b_0
-//       -------------------------------------
-//        s_k b_(k-1)  ...  s_3  s_2  s_1  s_0
+//        b_k     b_(k-1)  ...  b_2  b_1  b_0
+//      -         b_k      ...  b_3  b_2  b_1  b_0
+//       -----------------------------------------
+//        s_(k+1) s_k      ...  s_3  s_2  s_1  s_0
 //
 //     A left-shift followed by subtraction of the original value yields a new
-//     representation of the same value, using signed bits s_i = b_(i+1) - b_i.
+//     representation of the same value, using signed bits s_i = b_(i-1) - b_i.
 //     This representation from Booth's paper has since appeared in the
 //     literature under a variety of different names including "reversed binary
 //     form", "alternating greedy expansion", "mutual opposite form", and
@@ -73,7 +74,7 @@
 // (1961), pp. 67-91), in a radix-2^5 setting.  That is, we always combine five
 // signed bits into a signed digit:
 //
-//       s_(4j + 4) s_(4j + 3) s_(4j + 2) s_(4j + 1) s_(4j)
+//       s_(5j + 4) s_(5j + 3) s_(5j + 2) s_(5j + 1) s_(5j)
 //
 // The sign-alternating property implies that the resulting digit values are
 // integers from -16 to 16.
@@ -81,14 +82,164 @@
 // Of course, we don't actually need to compute the signed digits s_i as an
 // intermediate step (that's just a nice way to see how this scheme relates
 // to the wNAF): a direct computation obtains the recoded digit from the
-// six bits b_(4j + 4) ... b_(4j - 1).
+// six bits b_(5j + 4) ... b_(5j - 1).
 //
-// This function takes those five bits as an integer (0 .. 63), writing the
+// This function takes those six bits as an integer (0 .. 63), writing the
 // recoded digit to *sign (0 for positive, 1 for negative) and *digit (absolute
-// value, in the range 0 .. 8).  Note that this integer essentially provides the
-// input bits "shifted to the left" by one position: for example, the input to
-// compute the least significant recoded digit, given that there's no bit b_-1,
-// has to be b_4 b_3 b_2 b_1 b_0 0.
+// value, in the range 0 .. 16).  Note that this integer essentially provides
+// the input bits "shifted to the left" by one position: for example, the input
+// to compute the least significant recoded digit, given that there's no bit
+// b_-1, has to be b_4 b_3 b_2 b_1 b_0 0.
+//
+// DOUBLING CASE:
+//
+// Point addition formulas for short Weierstrass curves are often incomplete.
+// Edge cases such as P + P or P + ∞ must be handled separately. This
+// complicates constant-time requirements. P + ∞ cannot be avoided (any window
+// may be zero) and is handled with constant-time selects. P + P (where P is not
+// ∞) usually is not. Instead, windowing strategies are chosen to avoid this
+// case. Whether this happens depends on the group order.
+//
+// Let w be the window width (in this function, w = 5). The non-trivial doubling
+// case in single-point scalar multiplication may occur if and only if the
+// 2^(w-1) bit of the group order is zero.
+//
+// Note the above only holds if the scalar is fully reduced and the group order
+// is a prime that is much larger than 2^w. It also only holds when windows
+// are applied from most significant to least significant, doubling between each
+// window. It does not apply to more complex table strategies such as
+// |EC_GFp_nistz256_method|.
+//
+// PROOF:
+//
+// Let n be the group order. Let l be the number of bits needed to represent n.
+// Assume there exists some 0 <= k < n such that signed w-bit windowed
+// multiplication hits the doubling case.
+//
+// Windowed multiplication consists of iterating over groups of s_i (defined
+// above based on k's binary representation) from most to least significant. At
+// iteration i (for i = ..., 3w, 2w, w, 0, starting from the most significant
+// window), we:
+//
+//  1. Double the accumulator A, w times. Let A_i be the value of A at this
+//     point.
+//
+//  2. Set A to T_i + A_i, where T_i is a precomputed multiple of P
+//     corresponding to the window s_(i+w-1) ... s_i.
+//
+// Let j be the index such that A_j = T_j ≠ ∞. Looking at A_i and T_i as
+// multiples of P, define a_i and t_i to be scalar coefficients of A_i and T_i.
+// Thus a_j = t_j ≠ 0 (mod n). Note a_i and t_i may not be reduced mod n. t_i is
+// the value of the w signed bits s_(i+w-1) ... s_i. a_i is computed as a_i =
+// 2^w * (a_(i+w) + t_(i+w)).
+//
+// t_i is bounded by -2^(w-1) <= t_i <= 2^(w-1). Additionally, we may write it
+// in terms of unsigned bits b_i. t_i consists of signed bits s_(i+w-1) ... s_i.
+// This is computed as:
+//
+//         b_(i+w-2) b_(i+w-3)  ...  b_i      b_(i-1)
+//      -  b_(i+w-1) b_(i+w-2)  ...  b_(i+1)  b_i
+//       --------------------------------------------
+//   t_i = s_(i+w-1) s_(i+w-2)  ...  s_(i+1)  s_i
+//
+// Observe that b_(i+w-2) through b_i occur in both terms. Let x be the integer
+// represented by that bit string, i.e. 2^(w-2)*b_(i+w-2) + ... + b_i.
+//
+//   t_i = (2*x + b_(i-1)) - (2^(w-1)*b_(i+w-1) + x)
+//       = x - 2^(w-1)*b_(i+w-1) + b_(i-1)
+//
+// Or, using C notation for bit operations:
+//
+//   t_i = (k>>i) & ((1<<(w-1)) - 1) - (k>>i) & (1<<(w-1)) + (k>>(i-1)) & 1
+//
+// Note b_(i-1) is added in left-shifted by one (or doubled) from its place.
+// This is compensated by t_(i-w)'s subtraction term. Thus, a_i may be computed
+// by adding b_l b_(l-1) ... b_(i+1) b_i and an extra copy of b_(i-1). In C
+// notation, this is:
+//
+//   a_i = (k>>(i+w)) << w + ((k>>(i+w-1)) & 1) << w
+//
+// Observe that, while t_i may be positive or negative, a_i is bounded by
+// 0 <= a_i < n + 2^w. Additionally, a_i can only be zero if b_(i+w-1) and up
+// are all zero. (Note this implies a non-trivial P + (-P) is unreachable for
+// all groups. That would imply the subsequent a_i is zero, which means all
+// terms thus far were zero.)
+//
+// Returning to our doubling position, we have a_j = t_j (mod n). We now
+// determine the value of a_j - t_j, which must be divisible by n. Our bounds on
+// a_j and t_j imply a_j - t_j is 0 or n. If it is 0, a_j = t_j. However, 2^w
+// divides a_j and -2^(w-1) <= t_j <= 2^(w-1), so this can only happen if
+// a_j = t_j = 0, which is a trivial doubling. Therefore, a_j - t_j = n.
+//
+// Now we determine j. Suppose j > 0. w divides j, so j >= w. Then,
+//
+//   n = a_j - t_j = (k>>(j+w)) << w + ((k>>(j+w-1)) & 1) << w - t_j
+//                <= k/2^j + 2^w - t_j
+//                 < n/2^w + 2^w + 2^(w-1)
+//
+// n is much larger than 2^w, so this is impossible. Thus, j = 0: only the final
+// addition may hit the doubling case.
+//
+// Finally, we consider bit patterns for n and k. Divide k into k_H + k_M + k_L
+// such that k_H is the contribution from b_(l-1) .. b_w, k_M is the
+// contribution from b_(w-1), and k_L is the contribution from b_(w-2) ... b_0.
+// That is:
+//
+// - 2^w divides k_H
+// - k_M is 0 or 2^(w-1)
+// - 0 <= k_L < 2^(w-1)
+//
+// Divide n into n_H + n_M + n_L similarly. We thus have:
+//
+//   t_0 = (k>>0) & ((1<<(w-1)) - 1) - (k>>0) & (1<<(w-1)) + (k>>(0-1)) & 1
+//       = k & ((1<<(w-1)) - 1) - k & (1<<(w-1))
+//       = k_L - k_M
+//
+//   a_0 = (k>>(0+w)) << w + ((k>>(0+w-1)) & 1) << w
+//       = (k>>w) << w + ((k>>(w-1)) & 1) << w
+//       = k_H + 2*k_M
+//
+//                 n = a_0 - t_0
+//   n_H + n_M + n_L = (k_H + 2*k_M) - (k_L - k_M)
+//                   = k_H + 3*k_M - k_L
+//
+// k_H - k_L < k and k < n, so k_H - k_L ≠ n. Therefore k_M is not 0 and must be
+// 2^(w-1). Now we consider k_H and n_H. We know k_H <= n_H. Suppose k_H = n_H.
+// Then,
+//
+//   n_M + n_L = 3*(2^(w-1)) - k_L
+//             > 3*(2^(w-1)) - 2^(w-1)
+//             = 2^w
+//
+// Contradiction (n_M + n_L is the bottom w bits of n). Thus k_H < n_H. Suppose
+// k_H < n_H - 2*2^w. Then,
+//
+//   n_H + n_M + n_L = k_H + 3*(2^(w-1)) - k_L
+//                   < n_H - 2*2^w + 3*(2^(w-1)) - k_L
+//         n_M + n_L < -2^(w-1) - k_L
+//
+// Contradiction. Thus, k_H = n_H - 2^w. (Note 2^w divides n_H and k_H.) Thus,
+//
+//   n_H + n_M + n_L = k_H + 3*(2^(w-1)) - k_L
+//                   = n_H - 2^w + 3*(2^(w-1)) - k_L
+//         n_M + n_L = 2^(w-1) - k_L
+//                  <= 2^(w-1)
+//
+// Equality would mean 2^(w-1) divides n, which is impossible if n is prime.
+// Thus n_M + n_L < 2^(w-1), so n_M is zero, proving our condition.
+//
+// This proof constructs k, so, to show the converse, let k_H = n_H - 2^w,
+// k_M = 2^(w-1), k_L = 2^(w-1) - n_L. This will result in a non-trivial point
+// doubling in the final addition and is the only such scalar.
+//
+// COMMON CURVES:
+//
+// The group orders for common curves end in the following bit patterns:
+//
+//   P-521: ...00001001; w = 4 is okay
+//   P-384: ...01110011; w = 2, 5, 6, 7 are okay
+//   P-256: ...01010001; w = 5, 7 are okay
+//   P-224: ...00111101; w = 3, 4, 5, 6 are okay
 void ec_GFp_nistp_recode_scalar_bits(uint8_t *sign, uint8_t *digit,
                                      uint8_t in) {
   uint8_t s, d;
diff --git a/src/crypto/fipsmodule/ecdh/ecdh.c b/src/crypto/fipsmodule/ecdh/ecdh.c
index b9dc237..a7b2f08 100644
--- a/src/crypto/fipsmodule/ecdh/ecdh.c
+++ b/src/crypto/fipsmodule/ecdh/ecdh.c
@@ -93,7 +93,7 @@
   EC_RAW_POINT shared_point;
   uint8_t buf[EC_MAX_BYTES];
   size_t buflen;
-  if (!ec_point_mul_scalar(group, &shared_point, NULL, &pub_key->raw, priv) ||
+  if (!ec_point_mul_scalar(group, &shared_point, &pub_key->raw, priv) ||
       !ec_point_get_affine_coordinate_bytes(group, buf, NULL, &buflen,
                                             sizeof(buf), &shared_point)) {
     OPENSSL_PUT_ERROR(ECDH, ECDH_R_POINT_ARITHMETIC_FAILURE);
diff --git a/src/crypto/fipsmodule/ecdsa/ecdsa.c b/src/crypto/fipsmodule/ecdsa/ecdsa.c
index 010ee02..38771d5 100644
--- a/src/crypto/fipsmodule/ecdsa/ecdsa.c
+++ b/src/crypto/fipsmodule/ecdsa/ecdsa.c
@@ -232,7 +232,7 @@
     ec_scalar_from_montgomery(group, out_kinv_mont, out_kinv_mont);
 
     // Compute r, the x-coordinate of generator * k.
-    if (!ec_point_mul_scalar(group, &tmp_point, &k, NULL, NULL) ||
+    if (!ec_point_mul_scalar_base(group, &tmp_point, &k) ||
         !ec_get_x_coordinate_as_scalar(group, out_r, &tmp_point)) {
       goto err;
     }
diff --git a/src/crypto/fipsmodule/fips_shared.lds b/src/crypto/fipsmodule/fips_shared.lds
new file mode 100644
index 0000000..6d44abc
--- /dev/null
+++ b/src/crypto/fipsmodule/fips_shared.lds
@@ -0,0 +1,19 @@
+SECTIONS
+{
+  .text : {
+    BORINGSSL_bcm_text_start = .;
+    *(.text)
+    BORINGSSL_bcm_text_end = .;
+  }
+  .rodata : {
+    BORINGSSL_bcm_rodata_start = .;
+    *(.rodata)
+    BORINGSSL_bcm_rodata_end = .;
+  }
+
+  /DISCARD/ : {
+    *(.rela.dyn)
+    *(.data)
+    *(.rel.ro)
+  }
+}
diff --git a/src/crypto/fipsmodule/fips_shared_support.c b/src/crypto/fipsmodule/fips_shared_support.c
new file mode 100644
index 0000000..2a66a1f
--- /dev/null
+++ b/src/crypto/fipsmodule/fips_shared_support.c
@@ -0,0 +1,32 @@
+/* Copyright (c) 2019, 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. */
+
+#include <stdint.h>
+
+
+#if defined(BORINGSSL_FIPS) && defined(BORINGSSL_SHARED_LIBRARY)
+// BORINGSSL_bcm_text_hash is is default hash value for the FIPS integrity check
+// that must be replaced with the real value during the build process. This
+// value need only be distinct, i.e. so that we can safely search-and-replace it
+// in an object file.
+const uint8_t BORINGSSL_bcm_text_hash[64];
+const uint8_t BORINGSSL_bcm_text_hash[64] = {
+    0xae, 0x2c, 0xea, 0x2a, 0xbd, 0xa6, 0xf3, 0xec, 0x97, 0x7f, 0x9b,
+    0xf6, 0x94, 0x9a, 0xfc, 0x83, 0x68, 0x27, 0xcb, 0xa0, 0xa0, 0x9f,
+    0x6b, 0x6f, 0xde, 0x52, 0xcd, 0xe2, 0xcd, 0xff, 0x31, 0x80, 0xa2,
+    0xd4, 0xc3, 0x66, 0x0f, 0xc2, 0x6a, 0x7b, 0xf4, 0xbe, 0x39, 0xa2,
+    0xd7, 0x25, 0xdb, 0x21, 0x98, 0xe9, 0xd5, 0x53, 0xbf, 0x5c, 0x32,
+    0x06, 0x83, 0x34, 0x0c, 0x65, 0x89, 0x52, 0xbd, 0x1f,
+};
+#endif  // FIPS && SHARED_LIBRARY
diff --git a/src/crypto/fipsmodule/md5/asm/md5-586.pl b/src/crypto/fipsmodule/md5/asm/md5-586.pl
index ded9442..20c226b 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;
+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 6eb33c0..509bcde 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;
+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 b9edb79..d3e3763 100644
--- a/src/crypto/fipsmodule/modes/asm/aesni-gcm-x86_64.pl
+++ b/src/crypto/fipsmodule/modes/asm/aesni-gcm-x86_64.pl
@@ -1143,4 +1143,4 @@
 
 print $code;
 
-close 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 778b543..54c80f7 100644
--- a/src/crypto/fipsmodule/modes/asm/ghash-armv4.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghash-armv4.pl
@@ -553,4 +553,4 @@
 
 	print $_,"\n";
 }
-close 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 972be41..f57017d 100644
--- a/src/crypto/fipsmodule/modes/asm/ghash-neon-armv8.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghash-neon-armv8.pl
@@ -284,4 +284,4 @@
 
 	print $_,"\n";
 }
-close 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 0d9ce15..45e1ee1 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;
+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 1dd2519..e0e5010 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;
+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 02edf03..a2ab859 100644
--- a/src/crypto/fipsmodule/modes/asm/ghash-x86.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghash-x86.pl
@@ -1150,7 +1150,7 @@
 &asciz("GHASH for x86, CRYPTOGAMS by <appro\@openssl.org>");
 &asm_finish();
 
-close 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 b267698..5c4122f 100644
--- a/src/crypto/fipsmodule/modes/asm/ghash-x86_64.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghash-x86_64.pl
@@ -1803,4 +1803,4 @@
 
 print $code;
 
-close 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 c46cdb5..7a1259b 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; # 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 1435db5..99124a2 100644
--- a/src/crypto/fipsmodule/modes/asm/ghashv8-armx.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghashv8-armx.pl
@@ -425,4 +425,4 @@
     }
 }
 
-close STDOUT; # enforce flush
+close STDOUT or die "error closing STDOUT"; # enforce flush
diff --git a/src/crypto/fipsmodule/modes/ccm.c b/src/crypto/fipsmodule/modes/ccm.c
deleted file mode 100644
index 5a153f4..0000000
--- a/src/crypto/fipsmodule/modes/ccm.c
+++ /dev/null
@@ -1,256 +0,0 @@
-/* ====================================================================
- * Copyright (c) 2011 The OpenSSL Project.  All rights reserved.
- *
- * 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 above 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 acknowledgment:
- *    "This product includes software developed by the OpenSSL Project
- *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
- *
- * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
- *    endorse or promote products derived from this software without
- *    prior written permission. For written permission, please contact
- *    openssl-core@openssl.org.
- *
- * 5. Products derived from this software may not be called "OpenSSL"
- *    nor may "OpenSSL" appear in their names without prior written
- *    permission of the OpenSSL Project.
- *
- * 6. Redistributions of any form whatsoever must retain the following
- *    acknowledgment:
- *    "This product includes software developed by the OpenSSL Project
- *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
- *
- * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
- * EXPRESSED 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 OpenSSL PROJECT OR
- * ITS 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.
- * ====================================================================
- */
-
-#include <assert.h>
-#include <string.h>
-
-#include <openssl/cpu.h>
-#include <openssl/mem.h>
-
-#include "../../internal.h"
-#include "internal.h"
-
-
-struct ccm128_state {
-  union {
-    uint64_t u[2];
-    uint8_t c[16];
-  } nonce, cmac;
-};
-
-int CRYPTO_ccm128_init(CCM128_CONTEXT *ctx, const AES_KEY *key,
-                       block128_f block, ctr128_f ctr, unsigned M, unsigned L) {
-  if (M < 4 || M > 16 || (M & 1) != 0 || L < 2 || L > 8) {
-    return 0;
-  }
-  ctx->block = block;
-  ctx->ctr = ctr;
-  ctx->M = M;
-  ctx->L = L;
-  return 1;
-}
-
-size_t CRYPTO_ccm128_max_input(const CCM128_CONTEXT *ctx) {
-  return ctx->L >= sizeof(size_t) ? (size_t)-1
-                                  : (((size_t)1) << (ctx->L * 8)) - 1;
-}
-
-static int ccm128_init_state(const CCM128_CONTEXT *ctx,
-                             struct ccm128_state *state, const AES_KEY *key,
-                             const uint8_t *nonce, size_t nonce_len,
-                             const uint8_t *aad, size_t aad_len,
-                             size_t plaintext_len) {
-  const block128_f block = ctx->block;
-  const unsigned M = ctx->M;
-  const unsigned L = ctx->L;
-
-  // |L| determines the expected |nonce_len| and the limit for |plaintext_len|.
-  if (plaintext_len > CRYPTO_ccm128_max_input(ctx) ||
-      nonce_len != 15 - L) {
-    return 0;
-  }
-
-  // Assemble the first block for computing the MAC.
-  OPENSSL_memset(state, 0, sizeof(*state));
-  state->nonce.c[0] = (uint8_t)((L - 1) | ((M - 2) / 2) << 3);
-  if (aad_len != 0) {
-    state->nonce.c[0] |= 0x40;  // Set AAD Flag
-  }
-  OPENSSL_memcpy(&state->nonce.c[1], nonce, nonce_len);
-  for (unsigned i = 0; i < L; i++) {
-    state->nonce.c[15 - i] = (uint8_t)(plaintext_len >> (8 * i));
-  }
-
-  (*block)(state->nonce.c, state->cmac.c, key);
-  size_t blocks = 1;
-
-  if (aad_len != 0) {
-    unsigned i;
-    // Cast to u64 to avoid the compiler complaining about invalid shifts.
-    uint64_t aad_len_u64 = aad_len;
-    if (aad_len_u64 < 0x10000 - 0x100) {
-      state->cmac.c[0] ^= (uint8_t)(aad_len_u64 >> 8);
-      state->cmac.c[1] ^= (uint8_t)aad_len_u64;
-      i = 2;
-    } else if (aad_len_u64 <= 0xffffffff) {
-      state->cmac.c[0] ^= 0xff;
-      state->cmac.c[1] ^= 0xfe;
-      state->cmac.c[2] ^= (uint8_t)(aad_len_u64 >> 24);
-      state->cmac.c[3] ^= (uint8_t)(aad_len_u64 >> 16);
-      state->cmac.c[4] ^= (uint8_t)(aad_len_u64 >> 8);
-      state->cmac.c[5] ^= (uint8_t)aad_len_u64;
-      i = 6;
-    } else {
-      state->cmac.c[0] ^= 0xff;
-      state->cmac.c[1] ^= 0xff;
-      state->cmac.c[2] ^= (uint8_t)(aad_len_u64 >> 56);
-      state->cmac.c[3] ^= (uint8_t)(aad_len_u64 >> 48);
-      state->cmac.c[4] ^= (uint8_t)(aad_len_u64 >> 40);
-      state->cmac.c[5] ^= (uint8_t)(aad_len_u64 >> 32);
-      state->cmac.c[6] ^= (uint8_t)(aad_len_u64 >> 24);
-      state->cmac.c[7] ^= (uint8_t)(aad_len_u64 >> 16);
-      state->cmac.c[8] ^= (uint8_t)(aad_len_u64 >> 8);
-      state->cmac.c[9] ^= (uint8_t)aad_len_u64;
-      i = 10;
-    }
-
-    do {
-      for (; i < 16 && aad_len != 0; i++) {
-        state->cmac.c[i] ^= *aad;
-        aad++;
-        aad_len--;
-      }
-      (*block)(state->cmac.c, state->cmac.c, key);
-      blocks++;
-      i = 0;
-    } while (aad_len != 0);
-  }
-
-  // Per RFC 3610, section 2.6, the total number of block cipher operations done
-  // must not exceed 2^61. There are two block cipher operations remaining per
-  // message block, plus one block at the end to encrypt the MAC.
-  size_t remaining_blocks = 2 * ((plaintext_len + 15) / 16) + 1;
-  if (plaintext_len + 15 < plaintext_len ||
-      remaining_blocks + blocks < blocks ||
-      (uint64_t) remaining_blocks + blocks > UINT64_C(1) << 61) {
-    return 0;
-  }
-
-  // Assemble the first block for encrypting and decrypting. The bottom |L|
-  // bytes are replaced with a counter and all bit the encoding of |L| is
-  // cleared in the first byte.
-  state->nonce.c[0] &= 7;
-  return 1;
-}
-
-static int ccm128_encrypt(const CCM128_CONTEXT *ctx, struct ccm128_state *state,
-                          const AES_KEY *key, uint8_t *out, const uint8_t *in,
-                          size_t len) {
-  // The counter for encryption begins at one.
-  for (unsigned i = 0; i < ctx->L; i++) {
-    state->nonce.c[15 - i] = 0;
-  }
-  state->nonce.c[15] = 1;
-
-  uint8_t partial_buf[16];
-  unsigned num = 0;
-  if (ctx->ctr != NULL) {
-    CRYPTO_ctr128_encrypt_ctr32(in, out, len, key, state->nonce.c, partial_buf,
-                                &num, ctx->ctr);
-  } else {
-    CRYPTO_ctr128_encrypt(in, out, len, key, state->nonce.c, partial_buf, &num,
-                          ctx->block);
-  }
-  return 1;
-}
-
-static int ccm128_compute_mac(const CCM128_CONTEXT *ctx,
-                              struct ccm128_state *state, const AES_KEY *key,
-                              uint8_t *out_tag, size_t tag_len,
-                              const uint8_t *in, size_t len) {
-  block128_f block = ctx->block;
-  if (tag_len != ctx->M) {
-    return 0;
-  }
-
-  // Incorporate |in| into the MAC.
-  union {
-    uint64_t u[2];
-    uint8_t c[16];
-  } tmp;
-  while (len >= 16) {
-    OPENSSL_memcpy(tmp.c, in, 16);
-    state->cmac.u[0] ^= tmp.u[0];
-    state->cmac.u[1] ^= tmp.u[1];
-    (*block)(state->cmac.c, state->cmac.c, key);
-    in += 16;
-    len -= 16;
-  }
-  if (len > 0) {
-    for (size_t i = 0; i < len; i++) {
-      state->cmac.c[i] ^= in[i];
-    }
-    (*block)(state->cmac.c, state->cmac.c, key);
-  }
-
-  // Encrypt the MAC with counter zero.
-  for (unsigned i = 0; i < ctx->L; i++) {
-    state->nonce.c[15 - i] = 0;
-  }
-  (*block)(state->nonce.c, tmp.c, key);
-  state->cmac.u[0] ^= tmp.u[0];
-  state->cmac.u[1] ^= tmp.u[1];
-
-  OPENSSL_memcpy(out_tag, state->cmac.c, tag_len);
-  return 1;
-}
-
-int CRYPTO_ccm128_encrypt(const CCM128_CONTEXT *ctx, const AES_KEY *key,
-                          uint8_t *out, uint8_t *out_tag, size_t tag_len,
-                          const uint8_t *nonce, size_t nonce_len,
-                          const uint8_t *in, size_t len, const uint8_t *aad,
-                          size_t aad_len) {
-  struct ccm128_state state;
-  return ccm128_init_state(ctx, &state, key, nonce, nonce_len, aad, aad_len,
-                           len) &&
-         ccm128_compute_mac(ctx, &state, key, out_tag, tag_len, in, len) &&
-         ccm128_encrypt(ctx, &state, key, out, in, len);
-}
-
-int CRYPTO_ccm128_decrypt(const CCM128_CONTEXT *ctx, const AES_KEY *key,
-                          uint8_t *out, uint8_t *out_tag, size_t tag_len,
-                          const uint8_t *nonce, size_t nonce_len,
-                          const uint8_t *in, size_t len, const uint8_t *aad,
-                          size_t aad_len) {
-  struct ccm128_state state;
-  return ccm128_init_state(ctx, &state, key, nonce, nonce_len, aad, aad_len,
-                           len) &&
-         ccm128_encrypt(ctx, &state, key, out, in, len) &&
-         ccm128_compute_mac(ctx, &state, key, out_tag, tag_len, out, len);
-}
diff --git a/src/crypto/fipsmodule/modes/internal.h b/src/crypto/fipsmodule/modes/internal.h
index dec1e56..0971a90 100644
--- a/src/crypto/fipsmodule/modes/internal.h
+++ b/src/crypto/fipsmodule/modes/internal.h
@@ -345,42 +345,6 @@
 #endif  // GHASH_ASM
 
 
-// CCM.
-
-typedef struct ccm128_context {
-  block128_f block;
-  ctr128_f ctr;
-  unsigned M, L;
-} CCM128_CONTEXT;
-
-// CRYPTO_ccm128_init initialises |ctx| to use |block| (typically AES) with the
-// specified |M| and |L| parameters. It returns one on success and zero if |M|
-// or |L| is invalid.
-int CRYPTO_ccm128_init(CCM128_CONTEXT *ctx, const AES_KEY *key,
-                       block128_f block, ctr128_f ctr, unsigned M, unsigned L);
-
-// CRYPTO_ccm128_max_input returns the maximum input length accepted by |ctx|.
-size_t CRYPTO_ccm128_max_input(const CCM128_CONTEXT *ctx);
-
-// CRYPTO_ccm128_encrypt encrypts |len| bytes from |in| to |out| writing the tag
-// to |out_tag|. |key| must be the same key that was passed to
-// |CRYPTO_ccm128_init|. It returns one on success and zero otherwise.
-int CRYPTO_ccm128_encrypt(const CCM128_CONTEXT *ctx, const AES_KEY *key,
-                          uint8_t *out, uint8_t *out_tag, size_t tag_len,
-                          const uint8_t *nonce, size_t nonce_len,
-                          const uint8_t *in, size_t len, const uint8_t *aad,
-                          size_t aad_len);
-
-// CRYPTO_ccm128_decrypt decrypts |len| bytes from |in| to |out|, writing the
-// expected tag to |out_tag|. |key| must be the same key that was passed to
-// |CRYPTO_ccm128_init|. It returns one on success and zero otherwise.
-int CRYPTO_ccm128_decrypt(const CCM128_CONTEXT *ctx, const AES_KEY *key,
-                          uint8_t *out, uint8_t *out_tag, size_t tag_len,
-                          const uint8_t *nonce, size_t nonce_len,
-                          const uint8_t *in, size_t len, const uint8_t *aad,
-                          size_t aad_len);
-
-
 // CBC.
 
 // cbc128_f is the type of a function that performs CBC-mode encryption.
diff --git a/src/crypto/fipsmodule/rand/asm/rdrand-x86_64.pl b/src/crypto/fipsmodule/rand/asm/rdrand-x86_64.pl
index 76b5f9b..eb2a592 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;	# flush
+close STDOUT or die "error closing STDOUT";	# flush
diff --git a/src/crypto/fipsmodule/rsa/rsa_impl.c b/src/crypto/fipsmodule/rsa/rsa_impl.c
index 903ba9a..ab2abe9 100644
--- a/src/crypto/fipsmodule/rsa/rsa_impl.c
+++ b/src/crypto/fipsmodule/rsa/rsa_impl.c
@@ -554,7 +554,7 @@
   if (!ret) {
     OPENSSL_PUT_ERROR(RSA, RSA_R_PADDING_CHECK_FAILED);
   } else {
-    CONSTTIME_DECLASSIFY(out, out_len);
+    CONSTTIME_DECLASSIFY(out, *out_len);
   }
 
 err:
diff --git a/src/crypto/fipsmodule/sha/asm/sha1-586.pl b/src/crypto/fipsmodule/sha/asm/sha1-586.pl
index 87fd361..bf42961 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;
+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 27187dd..ca82514 100644
--- a/src/crypto/fipsmodule/sha/asm/sha1-armv4-large.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha1-armv4-large.pl
@@ -740,4 +740,4 @@
 	print $_,$/;
 }
 
-close 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 7c8880f..c147462 100644
--- a/src/crypto/fipsmodule/sha/asm/sha1-armv8.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha1-armv8.pl
@@ -353,4 +353,4 @@
 	print $_,"\n";
 }
 
-close 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 fd4ff2a..df7cbc3 100755
--- a/src/crypto/fipsmodule/sha/asm/sha1-x86_64.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha1-x86_64.pl
@@ -2122,4 +2122,4 @@
 
 	print $_,"\n";
 }
-close 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 129a9f4..240a604 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;
+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 e5ecdfd..15d78de 100644
--- a/src/crypto/fipsmodule/sha/asm/sha256-armv4.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha256-armv4.pl
@@ -735,4 +735,4 @@
 	print $_,"\n";
 }
 
-close 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 25a5f25..01acf67 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;
+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 cc247a4..c8c715e 100644
--- a/src/crypto/fipsmodule/sha/asm/sha512-armv4.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha512-armv4.pl
@@ -670,4 +670,4 @@
 close SELF;
 
 print $code;
-close 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 3f69071..64306be 100644
--- a/src/crypto/fipsmodule/sha/asm/sha512-armv8.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha512-armv8.pl
@@ -457,4 +457,4 @@
 	print $_,"\n";
 }
 
-close 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 4927850..8c5a5f3 100755
--- a/src/crypto/fipsmodule/sha/asm/sha512-x86_64.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha512-x86_64.pl
@@ -278,7 +278,6 @@
 	jnz	_shaext_shortcut
 ___
     # XOP codepath removed.
-___
 $code.=<<___ if ($avx>1);
 	and	\$`1<<8|1<<5|1<<3`,%r11d	# check for BMI2+AVX2+BMI1
 	cmp	\$`1<<8|1<<5|1<<3`,%r11d
@@ -2083,4 +2082,4 @@
 
 	print $_,"\n";
 }
-close STDOUT;
+close STDOUT or die "error closing STDOUT";
diff --git a/src/crypto/internal.h b/src/crypto/internal.h
index b80f065..76a317a 100644
--- a/src/crypto/internal.h
+++ b/src/crypto/internal.h
@@ -240,6 +240,36 @@
 #define CONSTTIME_TRUE_8 ((uint8_t)0xff)
 #define CONSTTIME_FALSE_8 ((uint8_t)0)
 
+// value_barrier_w returns |a|, but prevents GCC and Clang from reasoning about
+// the returned value. This is used to mitigate compilers undoing constant-time
+// code, until we can express our requirements directly in the language.
+//
+// Note the compiler is aware that |value_barrier_w| has no side effects and
+// always has the same output for a given input. This allows it to eliminate
+// dead code, move computations across loops, and vectorize.
+static inline crypto_word_t value_barrier_w(crypto_word_t a) {
+#if !defined(OPENSSL_NO_ASM) && (defined(__GNUC__) || defined(__clang__))
+  __asm__("" : "+r"(a) : /* no inputs */);
+#endif
+  return a;
+}
+
+// value_barrier_u32 behaves like |value_barrier_w| but takes a |uint32_t|.
+static inline uint32_t value_barrier_u32(uint32_t a) {
+#if !defined(OPENSSL_NO_ASM) && (defined(__GNUC__) || defined(__clang__))
+  __asm__("" : "+r"(a) : /* no inputs */);
+#endif
+  return a;
+}
+
+// value_barrier_u64 behaves like |value_barrier_w| but takes a |uint64_t|.
+static inline uint64_t value_barrier_u64(uint64_t a) {
+#if !defined(OPENSSL_NO_ASM) && (defined(__GNUC__) || defined(__clang__))
+  __asm__("" : "+r"(a) : /* no inputs */);
+#endif
+  return a;
+}
+
 // constant_time_msb_w returns the given value with the MSB copied to all the
 // other bits.
 static inline crypto_word_t constant_time_msb_w(crypto_word_t a) {
@@ -352,7 +382,13 @@
 static inline crypto_word_t constant_time_select_w(crypto_word_t mask,
                                                    crypto_word_t a,
                                                    crypto_word_t b) {
-  return (mask & a) | (~mask & b);
+  // Clang recognizes this pattern as a select. While it usually transforms it
+  // to a cmov, it sometimes further transforms it into a branch, which we do
+  // not want.
+  //
+  // Adding barriers to both |mask| and |~mask| breaks the relationship between
+  // the two, which makes the compiler stick with bitmasks.
+  return (value_barrier_w(mask) & a) | (value_barrier_w(~mask) & b);
 }
 
 // constant_time_select_8 acts like |constant_time_select| but operates on
diff --git a/src/crypto/mem.c b/src/crypto/mem.c
index 14e0bdf..0ca0e84 100644
--- a/src/crypto/mem.c
+++ b/src/crypto/mem.c
@@ -79,7 +79,10 @@
 static void __asan_unpoison_memory_region(const void *addr, size_t size) {}
 #endif
 
-#if defined(__GNUC__) || defined(__clang__)
+// Windows doesn't really support weak symbols as of May 2019, and Clang on
+// Windows will emit strong symbols instead. See
+// https://bugs.llvm.org/show_bug.cgi?id=37598
+#if defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER))
 // sdallocx is a sized |free| function. By passing the size (which we happen to
 // always know in BoringSSL), the malloc implementation can save work. We cannot
 // depend on |sdallocx| being available so we declare a wrapper that falls back
diff --git a/src/crypto/obj/obj_dat.h b/src/crypto/obj/obj_dat.h
index 0313a08..1a9bf15 100644
--- a/src/crypto/obj/obj_dat.h
+++ b/src/crypto/obj/obj_dat.h
@@ -57,7 +57,7 @@
 /* This file is generated by crypto/obj/objects.go. */
 
 
-#define NUM_NID 960
+#define NUM_NID 961
 
 static const uint8_t kObjectData[] = {
     /* NID_rsadsi */
@@ -8756,6 +8756,7 @@
     {"KxANY", "kx-any", NID_kx_any, 0, NULL, 0},
     {"AuthANY", "auth-any", NID_auth_any, 0, NULL, 0},
     {"CECPQ2", "CECPQ2", NID_CECPQ2, 0, NULL, 0},
+    {"CECPQ2b", "CECPQ2b", NID_CECPQ2b, 0, NULL, 0},
 };
 
 static const unsigned kNIDsInShortNameOrder[] = {
@@ -8818,6 +8819,7 @@
     109 /* CAST5-ECB */,
     111 /* CAST5-OFB */,
     959 /* CECPQ2 */,
+    960 /* CECPQ2b */,
     894 /* CMAC */,
     13 /* CN */,
     141 /* CRLReason */,
@@ -9723,6 +9725,7 @@
     179 /* CA Issuers */,
     785 /* CA Repository */,
     959 /* CECPQ2 */,
+    960 /* CECPQ2b */,
     131 /* Code Signing */,
     783 /* Diffie-Hellman based MAC */,
     382 /* Directory */,
diff --git a/src/crypto/obj/obj_mac.num b/src/crypto/obj/obj_mac.num
index 5fa839d..f2d4e8c 100644
--- a/src/crypto/obj/obj_mac.num
+++ b/src/crypto/obj/obj_mac.num
@@ -948,3 +948,4 @@
 kx_any		957
 auth_any		958
 CECPQ2		959
+CECPQ2b		960
diff --git a/src/crypto/obj/objects.txt b/src/crypto/obj/objects.txt
index 6dbb7ad..6e7ecf0 100644
--- a/src/crypto/obj/objects.txt
+++ b/src/crypto/obj/objects.txt
@@ -1337,6 +1337,9 @@
 # NID for CECPQ2 (no corresponding OID).
  : CECPQ2
 
+# NID for CECPQ2 (no corresponding OID).
+ : CECPQ2b
+
 # See RFC 8410.
 1 3 101 112 : ED25519
 
diff --git a/src/crypto/perlasm/arm-xlate.pl b/src/crypto/perlasm/arm-xlate.pl
index eb8ea59..655be61 100755
--- a/src/crypto/perlasm/arm-xlate.pl
+++ b/src/crypto/perlasm/arm-xlate.pl
@@ -168,6 +168,15 @@
     $line =~ s|^\s+||;		# ... and skip white spaces in beginning...
     $line =~ s|\s+$||;		# ... and at the end
 
+    if ($flavour =~ /64/) {
+	my $copy = $line;
+	# Also remove line comments.
+	$copy =~ s|//.*||;
+	if ($copy =~ /\b[wx]18\b/) {
+	    die "r18 is reserved by the platform and may not be used.";
+	}
+    }
+
     {
 	$line =~ s|[\b\.]L(\w{2,})|L$1|g;	# common denominator for Locallabel
 	$line =~ s|\bL(\w{2,})|\.L$1|g	if ($dotinlocallabels);
diff --git a/src/crypto/pkcs7/pkcs7.c b/src/crypto/pkcs7/pkcs7.c
index c04bffd..1d0b139 100644
--- a/src/crypto/pkcs7/pkcs7.c
+++ b/src/crypto/pkcs7/pkcs7.c
@@ -134,7 +134,7 @@
 int pkcs7_bundle(CBB *out, int (*cb)(CBB *out, const void *arg),
                  const void *arg) {
   CBB outer_seq, oid, wrapped_seq, seq, version_bytes, digest_algos_set,
-      content_info;
+      content_info, signer_infos;
 
   // See https://tools.ietf.org/html/rfc2315#section-7
   if (!CBB_add_asn1(out, &outer_seq, CBS_ASN1_SEQUENCE) ||
@@ -150,7 +150,8 @@
       !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)) {
+      !cb(&seq, arg) ||
+      !CBB_add_asn1(&seq, &signer_infos, CBS_ASN1_SET)) {
     return 0;
   }
 
diff --git a/src/crypto/pkcs7/pkcs7_test.cc b/src/crypto/pkcs7/pkcs7_test.cc
index 1ac9af2..948b44f 100644
--- a/src/crypto/pkcs7/pkcs7_test.cc
+++ b/src/crypto/pkcs7/pkcs7_test.cc
@@ -469,7 +469,7 @@
     "fNQMQoI9So4Vdy88Kow6BBBV3Lu6sZHue+cjxXETrmshNdNk8ABUMQA=\n"
     "-----END PKCS7-----\n";
 
-static void TestCertRepase(const uint8_t *der_bytes, size_t der_len) {
+static void TestCertReparse(const uint8_t *der_bytes, size_t der_len) {
   bssl::UniquePtr<STACK_OF(X509)> certs(sk_X509_new_null());
   ASSERT_TRUE(certs);
   bssl::UniquePtr<STACK_OF(X509)> certs2(sk_X509_new_null());
@@ -638,11 +638,11 @@
 }
 
 TEST(PKCS7Test, CertReparseNSS) {
-  TestCertRepase(kPKCS7NSS, sizeof(kPKCS7NSS));
+  TestCertReparse(kPKCS7NSS, sizeof(kPKCS7NSS));
 }
 
 TEST(PKCS7Test, CertReparseWindows) {
-  TestCertRepase(kPKCS7Windows, sizeof(kPKCS7Windows));
+  TestCertReparse(kPKCS7Windows, sizeof(kPKCS7Windows));
 }
 
 TEST(PKCS7Test, CrlReparse) {
diff --git a/src/crypto/pkcs8/p5_pbev2.c b/src/crypto/pkcs8/p5_pbev2.c
index 7497b00..e58cf44 100644
--- a/src/crypto/pkcs8/p5_pbev2.c
+++ b/src/crypto/pkcs8/p5_pbev2.c
@@ -81,6 +81,10 @@
 static const uint8_t kHMACWithSHA1[] = {0x2a, 0x86, 0x48, 0x86,
                                         0xf7, 0x0d, 0x02, 0x07};
 
+// 1.2.840.113549.2.9
+static const uint8_t kHMACWithSHA256[] = {0x2a, 0x86, 0x48, 0x86,
+                                          0xf7, 0x0d, 0x02, 0x09};
+
 static const struct {
   uint8_t oid[9];
   uint8_t oid_len;
@@ -140,18 +144,18 @@
 }
 
 static int pkcs5_pbe2_cipher_init(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
-                                  unsigned iterations, const char *pass,
-                                  size_t pass_len, const uint8_t *salt,
-                                  size_t salt_len, const uint8_t *iv,
-                                  size_t iv_len, int enc) {
+                                  const EVP_MD *pbkdf2_md, unsigned iterations,
+                                  const char *pass, size_t pass_len,
+                                  const uint8_t *salt, size_t salt_len,
+                                  const uint8_t *iv, size_t iv_len, int enc) {
   if (iv_len != EVP_CIPHER_iv_length(cipher)) {
     OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_ERROR_SETTING_CIPHER_PARAMS);
     return 0;
   }
 
   uint8_t key[EVP_MAX_KEY_LENGTH];
-  int ret = PKCS5_PBKDF2_HMAC_SHA1(pass, pass_len, salt, salt_len, iterations,
-                                   EVP_CIPHER_key_length(cipher), key) &&
+  int ret = PKCS5_PBKDF2_HMAC(pass, pass_len, salt, salt_len, iterations,
+                              pbkdf2_md, EVP_CIPHER_key_length(cipher), key) &&
             EVP_CipherInit_ex(ctx, cipher, NULL /* engine */, key, iv, enc);
   OPENSSL_cleanse(key, EVP_MAX_KEY_LENGTH);
   return ret;
@@ -201,9 +205,9 @@
     return 0;
   }
 
-  return pkcs5_pbe2_cipher_init(ctx, cipher, iterations, pass, pass_len, salt,
-                                salt_len, iv, EVP_CIPHER_iv_length(cipher),
-                                1 /* encrypt */);
+  return pkcs5_pbe2_cipher_init(ctx, cipher, EVP_sha1(), iterations, pass,
+                                pass_len, salt, salt_len, iv,
+                                EVP_CIPHER_iv_length(cipher), 1 /* encrypt */);
 }
 
 int PKCS5_pbe2_decrypt_init(const struct pbe_suite *suite, EVP_CIPHER_CTX *ctx,
@@ -264,6 +268,7 @@
     }
   }
 
+  const EVP_MD *md = EVP_sha1();
   if (CBS_len(&pbkdf2_params) != 0) {
     CBS alg_id, prf;
     if (!CBS_get_asn1(&pbkdf2_params, &alg_id, CBS_ASN1_SEQUENCE) ||
@@ -273,14 +278,18 @@
       return 0;
     }
 
-    // We only support hmacWithSHA1. It is the DEFAULT, so DER requires it be
-    // omitted, but we match OpenSSL in tolerating it being present.
-    if (!CBS_mem_equal(&prf, kHMACWithSHA1, sizeof(kHMACWithSHA1))) {
+    if (CBS_mem_equal(&prf, kHMACWithSHA1, sizeof(kHMACWithSHA1))) {
+      // hmacWithSHA1 is the DEFAULT, so DER requires it be omitted, but we
+      // match OpenSSL in tolerating it being present.
+      md = EVP_sha1();
+    } else if (CBS_mem_equal(&prf, kHMACWithSHA256, sizeof(kHMACWithSHA256))) {
+      md = EVP_sha256();
+    } else {
       OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_UNSUPPORTED_PRF);
       return 0;
     }
 
-    // hmacWithSHA1 has a NULL parameter.
+    // All supported PRFs use a NULL parameter.
     CBS null;
     if (!CBS_get_asn1(&alg_id, &null, CBS_ASN1_NULL) ||
         CBS_len(&null) != 0 ||
@@ -301,7 +310,7 @@
     return 0;
   }
 
-  return pkcs5_pbe2_cipher_init(ctx, cipher, (unsigned)iterations, pass,
+  return pkcs5_pbe2_cipher_init(ctx, cipher, md, (unsigned)iterations, pass,
                                 pass_len, CBS_data(&salt), CBS_len(&salt),
                                 CBS_data(&iv), CBS_len(&iv), 0 /* decrypt */);
 }
diff --git a/src/crypto/pkcs8/pkcs12_test.cc b/src/crypto/pkcs8/pkcs12_test.cc
index e6c0e56..d345006 100644
--- a/src/crypto/pkcs8/pkcs12_test.cc
+++ b/src/crypto/pkcs8/pkcs12_test.cc
@@ -683,9 +683,12 @@
     0xfe, 0x3a, 0x66, 0x47, 0x40, 0x49, 0x02, 0x02, 0x07, 0xd0,
 };
 
-// kPBES2 is a PKCS#12 file using PBES2 created with:
+// kPBES2WithSHA1 is a PKCS#12 file using PBES2 and HMAC-SHA-1 created with:
 // openssl pkcs12 -export -inkey key.pem -in cert.pem -keypbe AES-128-CBC -certpbe AES-128-CBC
-static const uint8_t kPBES2[] = {
+//
+// This was generated with an older OpenSSL, which used hmacWithSHA1 as the PRF.
+// (There is currently no way to specify the PRF in the pkcs12 command.)
+static const uint8_t kPBES2WithSHA1[] = {
   0x30, 0x82, 0x0a, 0x03, 0x02, 0x01, 0x03, 0x30, 0x82, 0x09, 0xc9, 0x06,
   0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82,
   0x09, 0xba, 0x04, 0x82, 0x09, 0xb6, 0x30, 0x82, 0x09, 0xb2, 0x30, 0x82,
@@ -901,6 +904,318 @@
   0xc3, 0x8c, 0xc6, 0x69, 0xdc, 0xc6, 0x7f, 0x54, 0xef, 0x04, 0x08, 0xf8,
   0x9c, 0x8b, 0x12, 0x27, 0xe8, 0xec, 0x65, 0x02, 0x02, 0x08, 0x00};
 
+// kPBES2WithSHA256 is a PKCS#12 file using PBES2 and HMAC-SHA-256. It was
+// generated in the same way as |kPBES2WithSHA1|, but using OpenSSL 1.1.1b,
+// which uses hmacWithSHA256 as the PRF.
+static const uint8_t kPBES2WithSHA256[] = {
+    0x30, 0x82, 0x0a, 0x7f, 0x02, 0x01, 0x03, 0x30, 0x82, 0x0a, 0x45, 0x06,
+    0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82,
+    0x0a, 0x36, 0x04, 0x82, 0x0a, 0x32, 0x30, 0x82, 0x0a, 0x2e, 0x30, 0x82,
+    0x04, 0xa2, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07,
+    0x06, 0xa0, 0x82, 0x04, 0x93, 0x30, 0x82, 0x04, 0x8f, 0x02, 0x01, 0x00,
+    0x30, 0x82, 0x04, 0x88, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+    0x01, 0x07, 0x01, 0x30, 0x57, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+    0x0d, 0x01, 0x05, 0x0d, 0x30, 0x4a, 0x30, 0x29, 0x06, 0x09, 0x2a, 0x86,
+    0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x1c, 0x04, 0x08, 0xb2,
+    0x5e, 0x0d, 0x6d, 0xda, 0xaa, 0x2f, 0xbe, 0x02, 0x02, 0x08, 0x00, 0x30,
+    0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09, 0x05,
+    0x00, 0x30, 0x1d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
+    0x01, 0x02, 0x04, 0x10, 0x3c, 0x04, 0x78, 0x37, 0xb3, 0xb2, 0x24, 0xd3,
+    0xb5, 0x46, 0x20, 0xb7, 0xd2, 0xdd, 0x5d, 0x2e, 0x80, 0x82, 0x04, 0x20,
+    0x3a, 0x01, 0xe4, 0xf4, 0x57, 0xd3, 0xed, 0x14, 0xd0, 0x42, 0x3f, 0xd3,
+    0x61, 0xee, 0x84, 0xcd, 0x2b, 0x08, 0x60, 0x30, 0xbd, 0x72, 0xa7, 0xd5,
+    0xa4, 0xf2, 0x13, 0xe9, 0xf0, 0x44, 0x66, 0x26, 0x34, 0xe7, 0x2c, 0x5d,
+    0xc9, 0xb0, 0x4b, 0xab, 0x47, 0x16, 0xab, 0xe6, 0x06, 0xa6, 0x3b, 0x79,
+    0x41, 0x0c, 0x79, 0xd5, 0x9b, 0x02, 0x67, 0xd8, 0x7f, 0xc8, 0x36, 0x37,
+    0x27, 0xb4, 0x44, 0xa2, 0x5e, 0x0d, 0x38, 0xb8, 0x41, 0x8e, 0x3a, 0xf1,
+    0xe9, 0xab, 0xe0, 0x19, 0xd0, 0xe1, 0xc7, 0x92, 0xd4, 0x5b, 0x35, 0xf3,
+    0x79, 0x48, 0x3b, 0xfc, 0x25, 0xfc, 0xc6, 0x9f, 0xed, 0x35, 0x28, 0x5b,
+    0xfa, 0xee, 0x50, 0x42, 0xa3, 0xc3, 0x96, 0xee, 0xe0, 0x87, 0x33, 0x5e,
+    0xa7, 0xc7, 0x0a, 0xfe, 0xda, 0xe5, 0xd5, 0x29, 0x6a, 0x57, 0x08, 0x7f,
+    0x56, 0x37, 0x2a, 0x1a, 0xa0, 0x6d, 0xe9, 0x84, 0xac, 0xed, 0x0e, 0xd8,
+    0xc0, 0xd8, 0xc6, 0x77, 0xb1, 0xdd, 0x1b, 0xa1, 0xed, 0xa7, 0x79, 0x13,
+    0x2e, 0x5b, 0x9b, 0x80, 0x44, 0x9e, 0xff, 0x0a, 0x6e, 0x99, 0x33, 0xcf,
+    0xf1, 0x47, 0x24, 0xaa, 0x48, 0xe7, 0x2c, 0xb3, 0xe6, 0xdc, 0xd4, 0x1e,
+    0xe4, 0xb8, 0x5e, 0x72, 0xaf, 0x3f, 0xd3, 0x25, 0x4a, 0xac, 0x7b, 0x35,
+    0xb1, 0x82, 0xa5, 0xd9, 0xf8, 0x01, 0x12, 0x92, 0x49, 0x4c, 0x17, 0x07,
+    0xb2, 0xb1, 0x3e, 0xcb, 0xfd, 0xd1, 0x17, 0xb5, 0x65, 0x3d, 0x0c, 0x2b,
+    0x2b, 0xc0, 0x37, 0x9c, 0xe7, 0x04, 0x9b, 0x71, 0x5a, 0x10, 0xc0, 0xba,
+    0x3b, 0x31, 0xde, 0x0d, 0x66, 0x6c, 0x0d, 0x4c, 0x99, 0x22, 0x76, 0x2a,
+    0x75, 0x7f, 0x84, 0xd1, 0x07, 0x1f, 0x57, 0xf0, 0x0b, 0x71, 0x41, 0xea,
+    0x38, 0xe2, 0xe7, 0xbe, 0x11, 0x3c, 0x92, 0x8c, 0x7b, 0x0e, 0xb4, 0x7e,
+    0x76, 0xc4, 0x80, 0x41, 0xae, 0x4c, 0xe2, 0x38, 0x36, 0xcb, 0x82, 0x39,
+    0x38, 0x3a, 0x55, 0xb4, 0xe2, 0x35, 0x94, 0xc3, 0xae, 0x3d, 0xd1, 0x03,
+    0xf3, 0xdb, 0x00, 0xd9, 0xfa, 0x96, 0x62, 0x25, 0x97, 0x51, 0xc5, 0xcf,
+    0x84, 0xe8, 0xf7, 0x8b, 0x2f, 0x31, 0xeb, 0xa7, 0x0a, 0x22, 0x6f, 0xad,
+    0xf5, 0x28, 0x25, 0xaa, 0x99, 0x0e, 0xb1, 0x83, 0x9f, 0x70, 0x79, 0xaf,
+    0x10, 0x7c, 0x2c, 0x55, 0xfe, 0x24, 0x7d, 0xea, 0x85, 0x48, 0x8e, 0x7a,
+    0xf7, 0x47, 0xd8, 0x0c, 0x64, 0x97, 0xe0, 0x8f, 0x62, 0x5e, 0xd0, 0x4f,
+    0x21, 0xa4, 0x46, 0x8e, 0x28, 0xb0, 0xb1, 0x90, 0xec, 0x01, 0x7d, 0xc4,
+    0xc8, 0x6f, 0xf2, 0xe2, 0xb7, 0xc4, 0x35, 0x6c, 0xa9, 0xf6, 0xaf, 0xc2,
+    0xb6, 0xa9, 0x02, 0x6d, 0xb2, 0x8b, 0x43, 0x6b, 0x41, 0x80, 0x9d, 0x5e,
+    0x51, 0xa7, 0x31, 0x00, 0x1b, 0xb5, 0x24, 0xed, 0x40, 0x99, 0x33, 0xde,
+    0x87, 0xd1, 0x4b, 0x76, 0x78, 0x57, 0x4c, 0x33, 0x79, 0x89, 0xd3, 0xfa,
+    0x70, 0x0f, 0x2f, 0x31, 0x42, 0x8c, 0xce, 0xe9, 0xc0, 0x58, 0xe1, 0x30,
+    0x30, 0xf1, 0xe9, 0xab, 0xc8, 0x60, 0x7c, 0xe0, 0x6a, 0x99, 0xe7, 0xd3,
+    0x21, 0x1a, 0xcc, 0x98, 0x60, 0x44, 0xaa, 0xff, 0xee, 0xec, 0x34, 0x20,
+    0x19, 0xba, 0x03, 0x3b, 0x67, 0x6f, 0xee, 0xd5, 0xb3, 0xa7, 0x21, 0x57,
+    0xd6, 0x49, 0xaf, 0x91, 0x8f, 0xec, 0x70, 0xd0, 0x59, 0x1a, 0x79, 0xe2,
+    0xd2, 0x94, 0x82, 0x53, 0xfb, 0xea, 0xd6, 0x83, 0x49, 0x4a, 0x6f, 0xd6,
+    0xed, 0x15, 0xc3, 0x71, 0x08, 0x3a, 0xbf, 0xde, 0xa8, 0x2d, 0x54, 0xaf,
+    0x4a, 0x40, 0xbc, 0xe5, 0x53, 0xae, 0x4b, 0x3d, 0x70, 0xfe, 0x1c, 0x03,
+    0x1e, 0xb2, 0x9d, 0x1c, 0x35, 0xbd, 0x9a, 0xf8, 0xc5, 0xd1, 0xa5, 0x4a,
+    0x63, 0x18, 0x02, 0xd4, 0xff, 0xdd, 0xcd, 0xb3, 0x6c, 0x38, 0xd1, 0x9a,
+    0xad, 0x16, 0x71, 0xf1, 0xc6, 0x1d, 0x8f, 0x6c, 0x30, 0xfa, 0x2e, 0x13,
+    0x9d, 0x0b, 0x4e, 0xe6, 0xd3, 0x37, 0x80, 0x58, 0x26, 0x0d, 0x04, 0x97,
+    0xe6, 0x8d, 0xcc, 0x63, 0x3c, 0x39, 0x38, 0x2f, 0x7a, 0x73, 0x01, 0x0f,
+    0x22, 0x69, 0x47, 0x54, 0x9e, 0x42, 0xc8, 0x59, 0xb5, 0x35, 0x43, 0xb4,
+    0x37, 0x45, 0x59, 0x85, 0xf2, 0x47, 0xc3, 0xfb, 0x23, 0x13, 0x18, 0xef,
+    0xd8, 0x11, 0x70, 0x74, 0xce, 0x97, 0xcf, 0xbf, 0xd5, 0x2d, 0x99, 0x00,
+    0x86, 0x56, 0x9b, 0xdf, 0x05, 0x67, 0xf4, 0x49, 0x1e, 0xb5, 0x12, 0x23,
+    0x46, 0x04, 0x83, 0xf3, 0xc1, 0x59, 0xc7, 0x7b, 0xc3, 0x22, 0x0c, 0x2c,
+    0x1b, 0x7d, 0x18, 0xb6, 0xd2, 0xfa, 0x28, 0x36, 0x8b, 0x51, 0x6d, 0x58,
+    0xf4, 0xd6, 0xdf, 0x38, 0x94, 0xcf, 0x6c, 0x50, 0x4f, 0x0a, 0xf3, 0xc3,
+    0x91, 0x39, 0xa5, 0xc9, 0xbc, 0xa8, 0xeb, 0x24, 0x1a, 0xdd, 0x58, 0x9e,
+    0xdc, 0xb2, 0xee, 0xe1, 0xa5, 0x16, 0x68, 0xc2, 0x63, 0x8c, 0xc9, 0xa7,
+    0xbe, 0x1e, 0x30, 0x84, 0xa6, 0x28, 0xeb, 0x50, 0xd9, 0xdd, 0x15, 0xea,
+    0x64, 0x34, 0xf0, 0x7a, 0x56, 0x6a, 0xdd, 0xb2, 0x70, 0x2e, 0xea, 0x72,
+    0x66, 0x39, 0x54, 0xaa, 0x36, 0xfa, 0x68, 0xaa, 0x06, 0x5d, 0x48, 0xca,
+    0xad, 0x4e, 0xfe, 0x4b, 0x40, 0xdf, 0x43, 0x46, 0xd6, 0xdf, 0x3f, 0xa1,
+    0x9e, 0x4c, 0xdc, 0xfe, 0x4c, 0x01, 0x09, 0x7f, 0xd8, 0x00, 0x84, 0x94,
+    0x29, 0x17, 0x67, 0x00, 0xd3, 0x46, 0xd2, 0xba, 0xb9, 0x62, 0x66, 0x50,
+    0xcd, 0x7c, 0x7a, 0x70, 0x46, 0x4a, 0x32, 0x62, 0xc2, 0x6e, 0xe7, 0x5e,
+    0x04, 0x24, 0xc5, 0xfd, 0x9d, 0xf4, 0x9b, 0xc8, 0xe9, 0xeb, 0x73, 0xf9,
+    0xaa, 0xa4, 0xcc, 0x63, 0xa3, 0xdc, 0x63, 0xe0, 0x30, 0xec, 0x70, 0x40,
+    0x9e, 0x7c, 0x63, 0x79, 0xae, 0xba, 0xfd, 0x95, 0x4c, 0x46, 0xf1, 0xc4,
+    0xae, 0xb9, 0x03, 0xe8, 0xd4, 0xe4, 0x90, 0x29, 0x3a, 0xbb, 0xdb, 0xd8,
+    0x8f, 0x40, 0xc3, 0x39, 0x9a, 0x4c, 0x70, 0x54, 0x9f, 0xc9, 0x0a, 0x04,
+    0x23, 0x98, 0x6b, 0x9c, 0xc2, 0xe0, 0xad, 0xae, 0x30, 0xef, 0xff, 0x44,
+    0x5b, 0x73, 0x2e, 0x8f, 0xd7, 0x2b, 0x12, 0xf0, 0x31, 0x08, 0xfb, 0xb9,
+    0x55, 0xf0, 0xc3, 0x62, 0xbb, 0x5f, 0x6d, 0xa7, 0x1d, 0x61, 0xc2, 0x26,
+    0xce, 0xab, 0xb6, 0x88, 0x25, 0xce, 0x8b, 0x02, 0xb6, 0xc5, 0xa2, 0xcc,
+    0xd4, 0xa3, 0x74, 0x5b, 0x76, 0xf7, 0xb4, 0xd9, 0x9c, 0x93, 0x86, 0x7e,
+    0xac, 0x82, 0xe0, 0x0d, 0x83, 0xe1, 0xc9, 0x7f, 0x2a, 0x86, 0xbb, 0xaa,
+    0xfe, 0xdc, 0x17, 0x9c, 0x28, 0x77, 0xe1, 0x58, 0x18, 0x15, 0x09, 0xe3,
+    0xda, 0xdb, 0x8d, 0xee, 0x55, 0xf6, 0xda, 0xad, 0xe5, 0x52, 0x84, 0xb4,
+    0xf0, 0x24, 0xce, 0xa1, 0x54, 0x4b, 0x9f, 0xea, 0x5d, 0x4d, 0x7f, 0x53,
+    0x0b, 0x79, 0x1d, 0x87, 0xcb, 0x0b, 0xa8, 0xef, 0x03, 0xfa, 0x58, 0x57,
+    0xf6, 0x02, 0x70, 0xdb, 0x7a, 0x64, 0x89, 0x1f, 0xc7, 0xca, 0x87, 0x02,
+    0x27, 0x33, 0xc5, 0x5b, 0x2a, 0x50, 0xc5, 0xb5, 0x7b, 0x2d, 0x3d, 0xa9,
+    0xbc, 0x21, 0x7b, 0xf2, 0xbe, 0x9c, 0x56, 0x35, 0x83, 0xba, 0xce, 0x34,
+    0x8d, 0xec, 0x7b, 0xaa, 0xe4, 0xcb, 0xd1, 0x4f, 0x4a, 0x31, 0x00, 0xd1,
+    0xb8, 0x30, 0x38, 0xaf, 0xe8, 0xe3, 0xd7, 0xc2, 0x8c, 0xe3, 0xb4, 0x23,
+    0xb3, 0x27, 0x07, 0xc6, 0x88, 0xec, 0x58, 0xe9, 0x59, 0xfb, 0xa9, 0x11,
+    0xa2, 0xc8, 0x77, 0x22, 0x6a, 0x5b, 0x86, 0xde, 0xdc, 0xed, 0x76, 0x6e,
+    0x73, 0x79, 0x5c, 0xb4, 0xcf, 0x19, 0x76, 0x5c, 0x6b, 0x1c, 0x4b, 0x03,
+    0xcb, 0x35, 0x08, 0x94, 0x37, 0x01, 0x98, 0x52, 0xd8, 0x31, 0x42, 0x3d,
+    0x7f, 0xa1, 0x11, 0x06, 0x07, 0x88, 0xb8, 0x31, 0x35, 0xb2, 0x49, 0x28,
+    0xc6, 0x2c, 0x44, 0x43, 0xb6, 0xbc, 0x58, 0x76, 0x6c, 0x4f, 0xc8, 0xb6,
+    0x30, 0x82, 0x05, 0x84, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+    0x01, 0x07, 0x01, 0xa0, 0x82, 0x05, 0x75, 0x04, 0x82, 0x05, 0x71, 0x30,
+    0x82, 0x05, 0x6d, 0x30, 0x82, 0x05, 0x69, 0x06, 0x0b, 0x2a, 0x86, 0x48,
+    0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x02, 0xa0, 0x82, 0x05, 0x31,
+    0x30, 0x82, 0x05, 0x2d, 0x30, 0x57, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+    0xf7, 0x0d, 0x01, 0x05, 0x0d, 0x30, 0x4a, 0x30, 0x29, 0x06, 0x09, 0x2a,
+    0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x1c, 0x04, 0x08,
+    0x79, 0x31, 0xf9, 0xe2, 0x42, 0x33, 0xf1, 0xaa, 0x02, 0x02, 0x08, 0x00,
+    0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09,
+    0x05, 0x00, 0x30, 0x1d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
+    0x04, 0x01, 0x02, 0x04, 0x10, 0xc9, 0xda, 0x5f, 0x96, 0xc8, 0x2c, 0x85,
+    0x5d, 0xa0, 0x30, 0x82, 0x16, 0x6b, 0xf4, 0xfd, 0x91, 0x04, 0x82, 0x04,
+    0xd0, 0xc3, 0x89, 0x6a, 0x56, 0x6a, 0x84, 0x58, 0x76, 0xd7, 0x23, 0xd5,
+    0xa8, 0xc1, 0x2f, 0x43, 0x38, 0x99, 0xf8, 0x64, 0x97, 0xe7, 0xe8, 0xd2,
+    0xcf, 0x36, 0x9b, 0x7e, 0x04, 0xe5, 0x87, 0x80, 0xff, 0xd5, 0x58, 0x50,
+    0x5a, 0xb3, 0xc0, 0x15, 0xc9, 0xd5, 0x61, 0xd6, 0x3b, 0x7f, 0x2f, 0x3b,
+    0x98, 0x50, 0x55, 0x09, 0xcf, 0xc3, 0xdd, 0xbd, 0x8b, 0xcd, 0xdf, 0x20,
+    0x90, 0xe1, 0xd2, 0xcd, 0x22, 0x9f, 0xa7, 0x3e, 0x10, 0xd3, 0xb7, 0x26,
+    0x54, 0x65, 0xfb, 0x18, 0x12, 0x58, 0x81, 0xd8, 0xe6, 0x97, 0xdf, 0x32,
+    0xd1, 0x04, 0x4a, 0xdb, 0x05, 0xb4, 0x13, 0xa9, 0x86, 0x62, 0x20, 0x94,
+    0xdc, 0xaf, 0x98, 0x53, 0x16, 0xc7, 0xb2, 0x9c, 0x44, 0x30, 0xc5, 0xaa,
+    0x14, 0x7a, 0x2d, 0x93, 0x20, 0xff, 0x6d, 0x8d, 0x47, 0x69, 0x6f, 0x39,
+    0xd4, 0x15, 0x81, 0x6b, 0x85, 0x36, 0xf9, 0x59, 0xa5, 0x8e, 0x5c, 0x40,
+    0x62, 0xf8, 0xfe, 0xf7, 0xe6, 0x75, 0xf7, 0x37, 0xfe, 0x5d, 0x53, 0xa6,
+    0x66, 0xe5, 0x0e, 0x4a, 0x23, 0xa9, 0x80, 0x4b, 0x04, 0x11, 0x0e, 0x50,
+    0xef, 0x9e, 0x88, 0xed, 0x39, 0xd1, 0x5f, 0xfa, 0x90, 0x22, 0xa3, 0x70,
+    0x0c, 0x8b, 0x20, 0x9c, 0x80, 0x2c, 0x90, 0x2e, 0x2c, 0xe0, 0xe6, 0x26,
+    0x84, 0xd8, 0x6a, 0xe4, 0x20, 0x1e, 0xbc, 0x96, 0xba, 0x07, 0x9d, 0x1d,
+    0x3d, 0x6c, 0xd1, 0x04, 0xc8, 0xd1, 0x79, 0x2c, 0x96, 0x0f, 0xe8, 0xa5,
+    0x6b, 0x03, 0x06, 0x51, 0xfd, 0x7b, 0x44, 0xab, 0x66, 0x4a, 0x41, 0x04,
+    0x02, 0x64, 0x5a, 0x40, 0x7d, 0x6b, 0x1a, 0xbc, 0x6e, 0xee, 0x68, 0x70,
+    0x3c, 0x10, 0x32, 0x73, 0x76, 0x28, 0x48, 0xd9, 0xa4, 0xe1, 0x21, 0xf6,
+    0xe4, 0x03, 0x94, 0x10, 0xef, 0x82, 0xe0, 0x76, 0x7c, 0x99, 0x30, 0x26,
+    0x9a, 0x95, 0xa2, 0xc5, 0xb9, 0xa7, 0xae, 0x9f, 0x85, 0xcb, 0xf1, 0x82,
+    0xcd, 0x3d, 0x06, 0xec, 0xaf, 0x72, 0xc1, 0x33, 0x09, 0xf9, 0x51, 0x94,
+    0x42, 0xf0, 0x69, 0xb9, 0xc6, 0x04, 0xe6, 0x7a, 0xfb, 0x1c, 0xee, 0xac,
+    0x95, 0x9b, 0x88, 0x67, 0x19, 0xa8, 0x79, 0x67, 0xc7, 0x1b, 0xcc, 0x72,
+    0xe9, 0x18, 0xd2, 0x96, 0xcf, 0x3d, 0xf8, 0x98, 0x20, 0x53, 0xc9, 0x37,
+    0x0f, 0x92, 0xb1, 0xbc, 0xaf, 0xc6, 0xec, 0x4f, 0x25, 0xda, 0x95, 0x14,
+    0xed, 0xb8, 0x3e, 0xaf, 0xd1, 0x52, 0x4c, 0x28, 0x3b, 0x84, 0x8c, 0x49,
+    0x34, 0x63, 0x2b, 0xd4, 0xf4, 0x78, 0xb1, 0x8f, 0xb0, 0x35, 0x7b, 0xd5,
+    0x44, 0xc3, 0x98, 0x9e, 0x85, 0x86, 0xae, 0xee, 0x05, 0xdd, 0xa1, 0x6f,
+    0x53, 0xe4, 0xdc, 0x6f, 0xf5, 0x7c, 0x7e, 0xd8, 0x7a, 0x9b, 0x18, 0x43,
+    0x3f, 0x7b, 0x2a, 0xf3, 0xb5, 0x39, 0x5a, 0x1c, 0x72, 0x3b, 0xdd, 0x01,
+    0x79, 0x97, 0xff, 0xdb, 0x58, 0xe5, 0x4d, 0x61, 0xde, 0xcf, 0x2f, 0x13,
+    0x7b, 0xaf, 0x6b, 0xa4, 0xf2, 0x59, 0x0a, 0x13, 0x56, 0x1c, 0x05, 0x00,
+    0x0f, 0x18, 0x66, 0x33, 0x72, 0xbd, 0x62, 0x8d, 0x11, 0xf7, 0x20, 0x52,
+    0x29, 0x42, 0x83, 0x33, 0xc1, 0x0f, 0x07, 0x80, 0xd4, 0x58, 0xe2, 0x22,
+    0x94, 0xad, 0xec, 0xbf, 0x01, 0xb6, 0x71, 0x7d, 0x92, 0xb1, 0x75, 0x14,
+    0xf2, 0xfb, 0x77, 0x39, 0x0d, 0x82, 0xb5, 0x51, 0xba, 0x1f, 0x65, 0x57,
+    0xaa, 0x68, 0x6a, 0x17, 0x41, 0x13, 0x38, 0xc0, 0xe5, 0xeb, 0xcc, 0x8c,
+    0xdd, 0xb7, 0x00, 0x4e, 0x01, 0x06, 0x25, 0xab, 0x87, 0x1c, 0x30, 0x69,
+    0xc4, 0x15, 0x0e, 0xf8, 0xf0, 0x72, 0xb6, 0x1d, 0x92, 0x7e, 0xe2, 0xe6,
+    0x77, 0xed, 0xb8, 0x3f, 0xcf, 0x57, 0x8d, 0x90, 0xe4, 0xa3, 0x79, 0x49,
+    0x9a, 0xe0, 0x1f, 0x4a, 0xde, 0xe9, 0x44, 0x8d, 0xd5, 0x23, 0x3b, 0x07,
+    0x63, 0x92, 0x9f, 0xde, 0xba, 0x7e, 0x67, 0xb0, 0x82, 0x41, 0x2a, 0xcd,
+    0xe1, 0xbb, 0x40, 0xf1, 0x8a, 0x66, 0x70, 0x74, 0xf1, 0x99, 0x7d, 0xb0,
+    0x0b, 0x6a, 0xa2, 0x5e, 0x7e, 0xc0, 0x8c, 0xb2, 0x71, 0xda, 0xcf, 0xbc,
+    0xfb, 0x9c, 0x03, 0x0e, 0x33, 0x5e, 0x13, 0xb2, 0x34, 0x38, 0xc1, 0x83,
+    0x95, 0xdf, 0x46, 0xfc, 0xe0, 0xe0, 0xaf, 0x93, 0xe0, 0x70, 0xd5, 0x15,
+    0x8c, 0x2f, 0xae, 0x4b, 0xa6, 0xeb, 0x13, 0x8f, 0xaf, 0x1b, 0xf5, 0x71,
+    0xc4, 0x62, 0x71, 0x08, 0x97, 0x10, 0x52, 0xfe, 0xbd, 0x60, 0xd7, 0x9f,
+    0xdf, 0x3d, 0xc5, 0xdd, 0xcd, 0xe7, 0x8e, 0x85, 0x60, 0xdf, 0x61, 0x79,
+    0x5b, 0x90, 0xd9, 0xaa, 0x56, 0x30, 0x6d, 0x0f, 0xfb, 0x27, 0x84, 0xdd,
+    0x3d, 0x04, 0x6a, 0xe0, 0x70, 0x7e, 0xbb, 0x59, 0xf4, 0xeb, 0xe8, 0xc0,
+    0x62, 0xaa, 0xf6, 0xed, 0xca, 0xae, 0xb2, 0x2b, 0x0f, 0xc1, 0x56, 0x45,
+    0xe7, 0x24, 0x6b, 0xaf, 0xeb, 0x15, 0x26, 0xb2, 0xcd, 0xae, 0x1f, 0xe7,
+    0x11, 0xc0, 0x1c, 0x19, 0x4a, 0xc7, 0x51, 0x2a, 0x29, 0xdf, 0x14, 0x82,
+    0x43, 0xfe, 0x52, 0x39, 0xba, 0xe6, 0x6c, 0xa5, 0x76, 0x8b, 0xb1, 0x21,
+    0x9c, 0x20, 0xb0, 0x10, 0x0c, 0x44, 0xf2, 0xd4, 0x6e, 0x41, 0x1b, 0x8f,
+    0x90, 0x23, 0xe3, 0x87, 0xfc, 0xf1, 0x46, 0xc6, 0x5b, 0xae, 0xd0, 0x2a,
+    0x2b, 0x78, 0xf5, 0x2b, 0xb9, 0x9f, 0x46, 0x4b, 0x30, 0xf8, 0x49, 0x57,
+    0x7e, 0xb4, 0xff, 0xca, 0xad, 0x4d, 0xf3, 0xc1, 0x7b, 0x42, 0xe0, 0xa4,
+    0x37, 0x2f, 0xe2, 0xb2, 0x60, 0xe8, 0xaf, 0xd7, 0x39, 0x23, 0x4c, 0x67,
+    0x44, 0xe5, 0x6d, 0xb3, 0x25, 0x11, 0x9f, 0x2b, 0xea, 0x23, 0xfb, 0x1e,
+    0xce, 0xbf, 0xa4, 0x2f, 0x88, 0xec, 0x18, 0x40, 0x16, 0x43, 0x9f, 0x71,
+    0x9c, 0x8d, 0xbd, 0x5d, 0x55, 0x3b, 0x92, 0x4e, 0x23, 0x3c, 0x87, 0xed,
+    0x5f, 0x2e, 0x8f, 0xde, 0x83, 0xad, 0x30, 0x42, 0x7e, 0x1a, 0x5e, 0xf5,
+    0xc5, 0x75, 0xbb, 0x99, 0x6e, 0xf1, 0x87, 0xe0, 0xf3, 0x51, 0x1e, 0x7d,
+    0xe8, 0xfc, 0xc6, 0x88, 0xf2, 0x39, 0x6d, 0xae, 0x73, 0x9f, 0xad, 0x9b,
+    0x7b, 0x67, 0x99, 0xdb, 0x90, 0x0e, 0xa0, 0xfc, 0xaf, 0xcc, 0xdb, 0x8b,
+    0xaa, 0xc2, 0x54, 0xd5, 0x2d, 0xb3, 0x5f, 0xa3, 0x0a, 0x3e, 0xd6, 0x8d,
+    0x40, 0x4d, 0x3b, 0xe5, 0x2d, 0x31, 0xd8, 0xb2, 0x12, 0x07, 0xca, 0x36,
+    0x56, 0xd9, 0x2f, 0x55, 0x82, 0xdc, 0x8e, 0x92, 0xa9, 0x6c, 0x91, 0x9e,
+    0x22, 0xe4, 0xc6, 0x27, 0x8b, 0x1a, 0xa2, 0x78, 0x56, 0x2c, 0x5a, 0x19,
+    0xdf, 0x40, 0xf9, 0xfb, 0x44, 0x21, 0x5b, 0xdf, 0x2f, 0x99, 0x84, 0x49,
+    0xcf, 0x1a, 0x15, 0xa5, 0x59, 0x3a, 0x66, 0x09, 0x4d, 0xc1, 0xf2, 0xb1,
+    0x24, 0x33, 0xbd, 0x86, 0x41, 0xdc, 0x33, 0x9b, 0x03, 0xc0, 0xa8, 0xf8,
+    0x94, 0x78, 0x2e, 0x16, 0x97, 0xef, 0x23, 0xee, 0xa4, 0xac, 0x3a, 0x90,
+    0xb6, 0xd9, 0xc0, 0xda, 0x5e, 0x26, 0x34, 0x26, 0xce, 0xc9, 0xf8, 0x45,
+    0x37, 0x83, 0x7c, 0xbd, 0x9c, 0x60, 0x40, 0x61, 0x28, 0xcd, 0x9c, 0xb4,
+    0xe4, 0xe6, 0x5c, 0x4f, 0xd1, 0x79, 0x42, 0x13, 0xa9, 0x6f, 0x26, 0x23,
+    0xc2, 0x6c, 0x8e, 0x8d, 0x7e, 0x3f, 0xee, 0x2b, 0x4d, 0xd2, 0x5b, 0x80,
+    0xdc, 0x74, 0xda, 0x1f, 0xbc, 0x26, 0x54, 0xc5, 0xfe, 0xee, 0xa9, 0x4f,
+    0xce, 0x46, 0xaf, 0x90, 0xb0, 0x12, 0x9a, 0x18, 0x0e, 0x06, 0x05, 0xc7,
+    0x98, 0xef, 0xcc, 0x6d, 0xa3, 0x46, 0x91, 0xa5, 0x0e, 0xe7, 0x35, 0x1a,
+    0x7f, 0x9d, 0xae, 0xa0, 0xb4, 0x0a, 0x32, 0x3b, 0xe4, 0xcd, 0x4b, 0x3e,
+    0x89, 0x73, 0xc9, 0x97, 0x38, 0xe5, 0x86, 0x4f, 0x24, 0xed, 0x4a, 0x43,
+    0x04, 0x02, 0xc1, 0x29, 0x8d, 0x85, 0xa2, 0xdd, 0xb2, 0x61, 0x3c, 0xce,
+    0x8b, 0x47, 0x2e, 0xed, 0x4b, 0x24, 0x94, 0xb7, 0xbf, 0x9d, 0x55, 0x42,
+    0x95, 0xc2, 0x27, 0xe5, 0x09, 0xd4, 0x20, 0x03, 0x20, 0x21, 0x3a, 0xd8,
+    0xd2, 0xa2, 0xb3, 0x47, 0x93, 0x4f, 0x5a, 0x39, 0xca, 0xd8, 0x74, 0xa9,
+    0x19, 0xa6, 0x9a, 0x23, 0xb1, 0x21, 0xa3, 0xb3, 0x14, 0xcc, 0xe2, 0x12,
+    0x91, 0x30, 0xdb, 0x50, 0xf8, 0x44, 0x74, 0xd6, 0x70, 0xdd, 0x7d, 0x26,
+    0x7f, 0xbf, 0x32, 0x93, 0x1f, 0x3d, 0x40, 0xbf, 0x2e, 0xec, 0x28, 0xf5,
+    0xb1, 0xaf, 0x11, 0xc7, 0x4e, 0x64, 0x13, 0x3c, 0xbf, 0x2e, 0x19, 0x81,
+    0xfe, 0x35, 0xba, 0xec, 0x6e, 0xb6, 0xa9, 0xfe, 0xc6, 0x85, 0x33, 0x41,
+    0x58, 0xab, 0x06, 0xae, 0x2b, 0x96, 0x62, 0x1f, 0x2c, 0x6c, 0xad, 0xec,
+    0x1a, 0x59, 0x55, 0x5a, 0x6f, 0xe0, 0xeb, 0x71, 0x8d, 0xb5, 0x0c, 0x81,
+    0x2a, 0x39, 0xbd, 0x67, 0x39, 0x48, 0xfb, 0x91, 0x64, 0xad, 0x01, 0x4c,
+    0x4a, 0x0f, 0x30, 0x29, 0xa0, 0xcf, 0x30, 0x96, 0x43, 0xe9, 0xfc, 0x22,
+    0x4b, 0xf3, 0x4f, 0xab, 0xec, 0xbc, 0x5a, 0xfb, 0x7f, 0x20, 0xd9, 0xd5,
+    0xc7, 0xce, 0x93, 0xa3, 0x2e, 0x82, 0xd1, 0xa0, 0xc6, 0x16, 0xd5, 0x64,
+    0x2d, 0x3f, 0x69, 0x15, 0xfd, 0xf3, 0x28, 0x3d, 0x4e, 0x61, 0x01, 0x2c,
+    0xd4, 0x2b, 0x40, 0x51, 0x6e, 0x95, 0x00, 0xa4, 0x34, 0x31, 0x25, 0x30,
+    0x23, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15,
+    0x31, 0x16, 0x04, 0x14, 0x47, 0xf4, 0x18, 0xa5, 0x4b, 0x85, 0xb7, 0x02,
+    0xc1, 0x97, 0xff, 0x57, 0xb6, 0x6f, 0x21, 0x45, 0x34, 0x3d, 0x92, 0x22,
+    0x30, 0x31, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02,
+    0x1a, 0x05, 0x00, 0x04, 0x14, 0x17, 0x45, 0x0c, 0xdf, 0x53, 0x76, 0x9b,
+    0xce, 0x3b, 0x12, 0xdd, 0x47, 0x05, 0x6d, 0x16, 0x90, 0x9d, 0x29, 0x9b,
+    0xe1, 0x04, 0x08, 0xa1, 0xf2, 0x82, 0x1c, 0xd1, 0xd1, 0x7b, 0x5c, 0x02,
+    0x02, 0x08, 0x00,
+};
+
+// kNoEncryption is a PKCS#12 file with neither the key or certificate is
+// encrypted. It was generated with:
+//
+//   openssl pkcs12 -export -inkey ecdsa_p256_key.pem -in ecdsa_p256_cert.pem -keypbe NONE -certpbe NONE -password pass:foo
+static const uint8_t kNoEncryption[] = {
+    0x30, 0x82, 0x03, 0x6e, 0x02, 0x01, 0x03, 0x30, 0x82, 0x03, 0x34, 0x06,
+    0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82,
+    0x03, 0x25, 0x04, 0x82, 0x03, 0x21, 0x30, 0x82, 0x03, 0x1d, 0x30, 0x82,
+    0x02, 0x3e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07,
+    0x01, 0xa0, 0x82, 0x02, 0x2f, 0x04, 0x82, 0x02, 0x2b, 0x30, 0x82, 0x02,
+    0x27, 0x30, 0x82, 0x02, 0x23, 0x06, 0x0b, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+    0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x03, 0xa0, 0x82, 0x01, 0xeb, 0x30, 0x82,
+    0x01, 0xe7, 0x06, 0x0a, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09,
+    0x16, 0x01, 0xa0, 0x82, 0x01, 0xd7, 0x04, 0x82, 0x01, 0xd3, 0x30, 0x82,
+    0x01, 0xcf, 0x30, 0x82, 0x01, 0x76, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02,
+    0x09, 0x00, 0xd9, 0x4c, 0x04, 0xda, 0x49, 0x7d, 0xbf, 0xeb, 0x30, 0x09,
+    0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01, 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, 0x30, 0x1e, 0x17, 0x0d,
+    0x31, 0x34, 0x30, 0x34, 0x32, 0x33, 0x32, 0x33, 0x32, 0x31, 0x35, 0x37,
+    0x5a, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x35, 0x32, 0x33, 0x32, 0x33, 0x32,
+    0x31, 0x35, 0x37, 0x5a, 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, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48,
+    0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03,
+    0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xe6, 0x2b, 0x69, 0xe2, 0xbf, 0x65,
+    0x9f, 0x97, 0xbe, 0x2f, 0x1e, 0x0d, 0x94, 0x8a, 0x4c, 0xd5, 0x97, 0x6b,
+    0xb7, 0xa9, 0x1e, 0x0d, 0x46, 0xfb, 0xdd, 0xa9, 0xa9, 0x1e, 0x9d, 0xdc,
+    0xba, 0x5a, 0x01, 0xe7, 0xd6, 0x97, 0xa8, 0x0a, 0x18, 0xf9, 0xc3, 0xc4,
+    0xa3, 0x1e, 0x56, 0xe2, 0x7c, 0x83, 0x48, 0xdb, 0x16, 0x1a, 0x1c, 0xf5,
+    0x1d, 0x7e, 0xf1, 0x94, 0x2d, 0x4b, 0xcf, 0x72, 0x22, 0xc1, 0xa3, 0x50,
+    0x30, 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04,
+    0x14, 0xab, 0x84, 0xd2, 0xac, 0xab, 0x95, 0xf0, 0x82, 0x4e, 0x16, 0x78,
+    0x07, 0x55, 0x57, 0x5f, 0xe4, 0x26, 0x8d, 0x82, 0xd1, 0x30, 0x1f, 0x06,
+    0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xab, 0x84,
+    0xd2, 0xac, 0xab, 0x95, 0xf0, 0x82, 0x4e, 0x16, 0x78, 0x07, 0x55, 0x57,
+    0x5f, 0xe4, 0x26, 0x8d, 0x82, 0xd1, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d,
+    0x13, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x09, 0x06, 0x07,
+    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01, 0x03, 0x48, 0x00, 0x30, 0x45,
+    0x02, 0x21, 0x00, 0xf2, 0xa0, 0x35, 0x5e, 0x51, 0x3a, 0x36, 0xc3, 0x82,
+    0x79, 0x9b, 0xee, 0x27, 0x50, 0x85, 0x8e, 0x70, 0x06, 0x74, 0x95, 0x57,
+    0xd2, 0x29, 0x74, 0x00, 0xf4, 0xbe, 0x15, 0x87, 0x5d, 0xc4, 0x07, 0x02,
+    0x20, 0x7c, 0x1e, 0x79, 0x14, 0x6a, 0x21, 0x83, 0xf0, 0x7a, 0x74, 0x68,
+    0x79, 0x5f, 0x14, 0x99, 0x9a, 0x68, 0xb4, 0xf1, 0xcb, 0x9e, 0x15, 0x5e,
+    0xe6, 0x1f, 0x32, 0x52, 0x61, 0x5e, 0x75, 0xc9, 0x14, 0x31, 0x25, 0x30,
+    0x23, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15,
+    0x31, 0x16, 0x04, 0x14, 0x3f, 0x31, 0x38, 0xec, 0xb9, 0xf1, 0x45, 0xe1,
+    0x3e, 0x90, 0x71, 0x0d, 0xc1, 0x28, 0xba, 0x4e, 0x6f, 0xa0, 0x9c, 0xed,
+    0x30, 0x81, 0xd8, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+    0x07, 0x01, 0xa0, 0x81, 0xca, 0x04, 0x81, 0xc7, 0x30, 0x81, 0xc4, 0x30,
+    0x81, 0xc1, 0x06, 0x0b, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c,
+    0x0a, 0x01, 0x01, 0xa0, 0x81, 0x8a, 0x30, 0x81, 0x87, 0x02, 0x01, 0x00,
+    0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06,
+    0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x04, 0x6d, 0x30,
+    0x6b, 0x02, 0x01, 0x01, 0x04, 0x20, 0x07, 0x0f, 0x08, 0x72, 0x7a, 0xd4,
+    0xa0, 0x4a, 0x9c, 0xdd, 0x59, 0xc9, 0x4d, 0x89, 0x68, 0x77, 0x08, 0xb5,
+    0x6f, 0xc9, 0x5d, 0x30, 0x77, 0x0e, 0xe8, 0xd1, 0xc9, 0xce, 0x0a, 0x8b,
+    0xb4, 0x6a, 0xa1, 0x44, 0x03, 0x42, 0x00, 0x04, 0xe6, 0x2b, 0x69, 0xe2,
+    0xbf, 0x65, 0x9f, 0x97, 0xbe, 0x2f, 0x1e, 0x0d, 0x94, 0x8a, 0x4c, 0xd5,
+    0x97, 0x6b, 0xb7, 0xa9, 0x1e, 0x0d, 0x46, 0xfb, 0xdd, 0xa9, 0xa9, 0x1e,
+    0x9d, 0xdc, 0xba, 0x5a, 0x01, 0xe7, 0xd6, 0x97, 0xa8, 0x0a, 0x18, 0xf9,
+    0xc3, 0xc4, 0xa3, 0x1e, 0x56, 0xe2, 0x7c, 0x83, 0x48, 0xdb, 0x16, 0x1a,
+    0x1c, 0xf5, 0x1d, 0x7e, 0xf1, 0x94, 0x2d, 0x4b, 0xcf, 0x72, 0x22, 0xc1,
+    0x31, 0x25, 0x30, 0x23, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+    0x01, 0x09, 0x15, 0x31, 0x16, 0x04, 0x14, 0x3f, 0x31, 0x38, 0xec, 0xb9,
+    0xf1, 0x45, 0xe1, 0x3e, 0x90, 0x71, 0x0d, 0xc1, 0x28, 0xba, 0x4e, 0x6f,
+    0xa0, 0x9c, 0xed, 0x30, 0x31, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b,
+    0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14, 0xd0, 0xb4, 0x17, 0x1a,
+    0xdb, 0xa3, 0x27, 0xd8, 0x9e, 0xd3, 0xf2, 0xb3, 0x3e, 0x96, 0x07, 0x3a,
+    0xf2, 0x6a, 0xc2, 0x1c, 0x04, 0x08, 0xb5, 0xa8, 0xb9, 0xdb, 0x2f, 0xf1,
+    0xa4, 0xcd, 0x02, 0x02, 0x08, 0x00,
+};
+
 static const char kPassword[] = "foo";
 
 // Generated with
@@ -1230,7 +1545,12 @@
 }
 
 TEST(PKCS12Test, TestPBES2) {
-  TestImpl("PBES2", kPBES2, kPassword, nullptr);
+  TestImpl("kPBES2WithSHA1", kPBES2WithSHA1, kPassword, nullptr);
+  TestImpl("kPBES2WithSHA256", kPBES2WithSHA256, kPassword, nullptr);
+}
+
+TEST(PKCS12Test, TestNoEncryption) {
+  TestImpl("kNoEncryption", kNoEncryption, kPassword, nullptr);
 }
 
 TEST(PKCS12Test, TestEmptyPassword) {
@@ -1506,3 +1826,152 @@
                 NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
                 NID_pbe_WithSHA1And3_Key_TripleDES_CBC, 100, 100);
 }
+
+static bssl::UniquePtr<EVP_PKEY> MakeTestKey() {
+  bssl::UniquePtr<EC_KEY> ec_key(
+      EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+  if (!ec_key ||
+      !EC_KEY_generate_key(ec_key.get())) {
+    return nullptr;
+  }
+  bssl::UniquePtr<EVP_PKEY> evp_pkey(EVP_PKEY_new());
+  if (!evp_pkey ||
+      !EVP_PKEY_assign_EC_KEY(evp_pkey.get(), ec_key.release())) {
+    return nullptr;
+  }
+  return evp_pkey;
+}
+
+static bssl::UniquePtr<X509> MakeTestCert(EVP_PKEY *key) {
+  bssl::UniquePtr<X509> x509(X509_new());
+  if (!x509) {
+    return nullptr;
+  }
+  X509_NAME* subject = X509_get_subject_name(x509.get());
+  if (!X509_gmtime_adj(X509_get_notBefore(x509.get()), 0) ||
+      !X509_gmtime_adj(X509_get_notAfter(x509.get()), 60 * 60 * 24) ||
+      !X509_NAME_add_entry_by_txt(subject, "CN", MBSTRING_ASC,
+                                  reinterpret_cast<const uint8_t *>("Test"), -1,
+                                  -1, 0) ||
+      !X509_set_issuer_name(x509.get(), subject) ||
+      !X509_set_pubkey(x509.get(), key) ||
+      !X509_sign(x509.get(), key, EVP_sha256())) {
+    return nullptr;
+  }
+  return x509;
+}
+
+static bool PKCS12CreateVector(std::vector<uint8_t> *out, EVP_PKEY *pkey,
+                               const std::vector<X509 *> &certs) {
+  bssl::UniquePtr<STACK_OF(X509)> chain(sk_X509_new_null());
+  if (!chain) {
+    return false;
+  }
+
+  for (X509 *cert : certs) {
+    if (!bssl::PushToStack(chain.get(), bssl::UpRef(cert))) {
+      return false;
+    }
+  }
+
+  bssl::UniquePtr<PKCS12> p12(PKCS12_create(kPassword, nullptr /* name */, pkey,
+                                            nullptr /* cert */, chain.get(), 0,
+                                            0, 0, 0, 0));
+  if (!p12) {
+    return false;
+  }
+
+  int len = i2d_PKCS12(p12.get(), nullptr);
+  if (len < 0) {
+    return false;
+  }
+  out->resize(static_cast<size_t>(len));
+  uint8_t *ptr = out->data();
+  return i2d_PKCS12(p12.get(), &ptr) == len;
+}
+
+static void ExpectPKCS12Parse(bssl::Span<const uint8_t> in,
+                              EVP_PKEY *expect_key, X509 *expect_cert,
+                              const std::vector<X509 *> &expect_ca_certs) {
+  bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(in.data(), in.size()));
+  ASSERT_TRUE(bio);
+
+  bssl::UniquePtr<PKCS12> p12(d2i_PKCS12_bio(bio.get(), nullptr));
+  ASSERT_TRUE(p12);
+
+  EVP_PKEY *key = nullptr;
+  X509 *cert = nullptr;
+  STACK_OF(X509) *ca_certs = nullptr;
+  ASSERT_TRUE(PKCS12_parse(p12.get(), kPassword, &key, &cert, &ca_certs));
+
+  bssl::UniquePtr<EVP_PKEY> delete_key(key);
+  bssl::UniquePtr<X509> delete_cert(cert);
+  bssl::UniquePtr<STACK_OF(X509)> delete_ca_certs(ca_certs);
+
+  if (expect_key == nullptr) {
+    EXPECT_FALSE(key);
+  } else {
+    ASSERT_TRUE(key);
+    EXPECT_EQ(1, EVP_PKEY_cmp(key, expect_key));
+  }
+
+  if (expect_cert == nullptr) {
+    EXPECT_FALSE(cert);
+  } else {
+    ASSERT_TRUE(cert);
+    EXPECT_EQ(0, X509_cmp(cert, expect_cert));
+  }
+
+  ASSERT_EQ(expect_ca_certs.size(), sk_X509_num(ca_certs));
+  for (size_t i = 0; i < expect_ca_certs.size(); i++) {
+    EXPECT_EQ(0, X509_cmp(expect_ca_certs[i], sk_X509_value(ca_certs, i)));
+  }
+}
+
+// Test that |PKCS12_parse| returns values in the expected order.
+TEST(PKCS12Test, Order) {
+  bssl::UniquePtr<EVP_PKEY> key1 = MakeTestKey();
+  ASSERT_TRUE(key1);
+  bssl::UniquePtr<X509> cert1 = MakeTestCert(key1.get());
+  ASSERT_TRUE(cert1);
+  bssl::UniquePtr<X509> cert1b = MakeTestCert(key1.get());
+  ASSERT_TRUE(cert1b);
+  bssl::UniquePtr<EVP_PKEY> key2 = MakeTestKey();
+  ASSERT_TRUE(key2);
+  bssl::UniquePtr<X509> cert2 = MakeTestCert(key2.get());
+  ASSERT_TRUE(cert2);
+  bssl::UniquePtr<EVP_PKEY> key3 = MakeTestKey();
+  ASSERT_TRUE(key3);
+  bssl::UniquePtr<X509> cert3 = MakeTestCert(key3.get());
+  ASSERT_TRUE(cert3);
+
+  // PKCS12_parse uses the key to select the main certificate.
+  std::vector<uint8_t> p12;
+  ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(),
+                                 {cert1.get(), cert2.get(), cert3.get()}));
+  ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert2.get(), cert3.get()});
+
+  ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(),
+                                 {cert3.get(), cert1.get(), cert2.get()}));
+  ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert3.get(), cert2.get()});
+
+  ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(),
+                                 {cert2.get(), cert3.get(), cert1.get()}));
+  ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert2.get(), cert3.get()});
+
+  // In case of duplicates, the last one is selected. (It is unlikely anything
+  // depends on which is selected, but we match OpenSSL.)
+  ASSERT_TRUE(
+      PKCS12CreateVector(&p12, key1.get(), {cert1.get(), cert1b.get()}));
+  ExpectPKCS12Parse(p12, key1.get(), cert1b.get(), {cert1.get()});
+
+  // If there is no key, all certificates are returned as "CA" certificates.
+  ASSERT_TRUE(PKCS12CreateVector(&p12, nullptr,
+                                 {cert1.get(), cert2.get(), cert3.get()}));
+  ExpectPKCS12Parse(p12, nullptr, nullptr,
+                    {cert1.get(), cert2.get(), cert3.get()});
+
+  // The same happens if there is a key, but it does not match any certificate.
+  ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(), {cert2.get(), cert3.get()}));
+  ExpectPKCS12Parse(p12, key1.get(), nullptr, {cert2.get(), cert3.get()});
+}
diff --git a/src/crypto/pkcs8/pkcs8.c b/src/crypto/pkcs8/pkcs8.c
index ee25ee2..a19b4a3 100644
--- a/src/crypto/pkcs8/pkcs8.c
+++ b/src/crypto/pkcs8/pkcs8.c
@@ -486,6 +486,10 @@
     goto err;
   }
 
+  // TODO(davidben): OpenSSL has since extended |pbe_nid| to control either the
+  // PBES1 scheme or the PBES2 PRF. E.g. passing |NID_hmacWithSHA256| will
+  // select PBES2 with HMAC-SHA256 as the PRF. Implement this if anything uses
+  // it. See 5693a30813a031d3921a016a870420e7eb93ec90 in OpenSSL.
   int alg_ok;
   if (pbe_nid == -1) {
     alg_ok = PKCS5_pbe2_encrypt_init(&epki, &ctx, cipher, (unsigned)iterations,
diff --git a/src/crypto/pkcs8/pkcs8_test.cc b/src/crypto/pkcs8/pkcs8_test.cc
index df275fb..beb532f 100644
--- a/src/crypto/pkcs8/pkcs8_test.cc
+++ b/src/crypto/pkcs8/pkcs8_test.cc
@@ -22,14 +22,15 @@
 #include "../internal.h"
 
 
-// kDER is a PKCS#8 encrypted private key. It was generated with:
+// kEncryptedPBES2WithDESAndSHA1 is a PKCS#8 encrypted private key using PBES2
+// with DES-EDE3-CBC and HMAC-SHA-1. It was generated with:
 //
 // openssl genrsa 512 > test.key
-// openssl pkcs8 -topk8 -in test.key -out test.key.encrypted -v2 des3 -outform der
+// openssl pkcs8 -topk8 -in test.key -out test.key.encrypted -v2 des3 -v2prf hmacWithSHA1 -outform der
 // hexdump -Cv test.key.encrypted
 //
 // The password is "testing".
-static const uint8_t kDER[] = {
+static const uint8_t kEncryptedPBES2WithDESAndSHA1[] = {
   0x30, 0x82, 0x01, 0x9e, 0x30, 0x40, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05,
   0x0d, 0x30, 0x33, 0x30, 0x1b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c,
   0x30, 0x0e, 0x04, 0x08, 0x06, 0xa5, 0x4b, 0x0c, 0x0c, 0x50, 0x8c, 0x19, 0x02, 0x02, 0x08, 0x00,
@@ -59,6 +60,55 @@
   0xd6, 0x2d,
 };
 
+// kEncryptedPBES2WithAESAndSHA256 is a PKCS#8 encrypted private key using PBES2
+// with AES-128-CBC and HMAC-SHA-256. It was generated with:
+//
+// openssl genrsa 512 > test.key
+// openssl pkcs8 -topk8 -in test.key -out test.key.encrypted -v2 aes-128-cbc -v2prf hmacWithSHA256 -outform der
+// hexdump -Cv test.key.encrypted
+//
+// The password is "testing".
+static const uint8_t kEncryptedPBES2WithAESAndSHA256[] = {
+    0x30, 0x82, 0x01, 0xbd, 0x30, 0x57, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+    0xf7, 0x0d, 0x01, 0x05, 0x0d, 0x30, 0x4a, 0x30, 0x29, 0x06, 0x09, 0x2a,
+    0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x1c, 0x04, 0x08,
+    0xd0, 0x39, 0xb7, 0x6d, 0xd0, 0xff, 0x85, 0xa8, 0x02, 0x02, 0x08, 0x00,
+    0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09,
+    0x05, 0x00, 0x30, 0x1d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
+    0x04, 0x01, 0x02, 0x04, 0x10, 0x95, 0xc6, 0x49, 0xc1, 0x61, 0x70, 0x74,
+    0x36, 0xfe, 0xc0, 0x98, 0x19, 0x26, 0x32, 0x9e, 0x1a, 0x04, 0x82, 0x01,
+    0x60, 0x87, 0xe3, 0x4d, 0xff, 0x33, 0x2b, 0x25, 0x07, 0x5f, 0x99, 0x24,
+    0x70, 0x40, 0x29, 0xb4, 0xaa, 0xce, 0xdd, 0x93, 0x60, 0x5c, 0x65, 0xec,
+    0xba, 0xee, 0xf8, 0x5e, 0xc7, 0x8f, 0x3e, 0x49, 0x00, 0xdf, 0xa7, 0xb2,
+    0xbb, 0xa7, 0xf7, 0x74, 0x56, 0xe0, 0xeb, 0x33, 0xe2, 0x30, 0x60, 0x3b,
+    0xd3, 0x34, 0x7b, 0xb0, 0x76, 0xc0, 0xed, 0xa2, 0x7d, 0x51, 0x99, 0x4d,
+    0x52, 0x1c, 0x9e, 0xd7, 0x28, 0x58, 0x32, 0xcf, 0x55, 0xf0, 0x19, 0x87,
+    0x5e, 0x66, 0x69, 0x00, 0x75, 0xd7, 0x68, 0x98, 0x64, 0x6a, 0x1e, 0x8c,
+    0xef, 0x7b, 0xbc, 0x9e, 0xed, 0xaf, 0x67, 0xcf, 0xc4, 0x6c, 0x40, 0x8a,
+    0x23, 0x73, 0xe6, 0x79, 0x5f, 0x7a, 0x15, 0x53, 0x2c, 0x70, 0x3b, 0x69,
+    0xba, 0x3d, 0xfb, 0x6e, 0xb9, 0x27, 0x78, 0x9b, 0xc3, 0x4b, 0xb8, 0xe3,
+    0xe7, 0x31, 0x82, 0x43, 0xee, 0x81, 0x5b, 0x01, 0x3b, 0x7e, 0x23, 0xb1,
+    0xcc, 0x20, 0xc9, 0xe0, 0xca, 0x9d, 0xaa, 0x9c, 0xe9, 0x47, 0x96, 0x0d,
+    0xc4, 0x1e, 0x1b, 0x1f, 0xb1, 0xee, 0x2b, 0x99, 0xaf, 0x2a, 0xb4, 0x9f,
+    0x29, 0xb5, 0x9b, 0x83, 0x5e, 0xe5, 0x7e, 0xf7, 0xf7, 0x58, 0x31, 0x54,
+    0xf6, 0x29, 0x6b, 0xfc, 0x85, 0x1d, 0x8a, 0x5f, 0x22, 0x03, 0xed, 0xce,
+    0x06, 0x5b, 0x93, 0x57, 0x23, 0x4e, 0x0e, 0x5b, 0xf8, 0x29, 0x29, 0x7c,
+    0x2d, 0x4a, 0xc6, 0x9a, 0x97, 0x64, 0x4c, 0x15, 0x72, 0xee, 0xaa, 0xd9,
+    0xed, 0xfd, 0x8f, 0xa2, 0x36, 0xf5, 0xf8, 0xe7, 0xf7, 0xfe, 0x67, 0x4b,
+    0xbe, 0x85, 0xe3, 0xda, 0x3a, 0xcc, 0x5f, 0x17, 0xbe, 0xa6, 0x1a, 0x38,
+    0xed, 0x85, 0x4a, 0xd6, 0x05, 0x4b, 0x5f, 0x54, 0x0e, 0xfd, 0xdc, 0x32,
+    0x9a, 0x0d, 0xca, 0xec, 0x02, 0x3f, 0x78, 0x23, 0x6e, 0x6a, 0xfa, 0x2c,
+    0x04, 0x86, 0xc8, 0x74, 0xa6, 0xf7, 0x2f, 0x1a, 0xf1, 0x1a, 0x51, 0x09,
+    0x2b, 0x04, 0xdc, 0x53, 0xaf, 0x39, 0x85, 0x35, 0x81, 0xc3, 0x11, 0xf0,
+    0x7d, 0xb7, 0xb3, 0x77, 0x0e, 0x8e, 0x22, 0xda, 0x1f, 0xb0, 0x3d, 0x59,
+    0xca, 0xa9, 0x70, 0xc3, 0x33, 0x66, 0xcb, 0x81, 0xfc, 0x85, 0x2a, 0xf0,
+    0xbc, 0xd0, 0x30, 0x85, 0x1b, 0xe6, 0x9b, 0x76, 0x88, 0xca, 0xf1, 0xf5,
+    0xc8, 0x19, 0x8a, 0x62, 0x16, 0x7b, 0x01, 0xc1, 0x1d, 0x80, 0xc0, 0xf7,
+    0xc2, 0x0b, 0xe3, 0xbf, 0x83, 0x9a, 0x3c, 0xc0, 0x0f, 0x5e, 0xea, 0xf7,
+    0xb2, 0x4c, 0xcd, 0x5e, 0xf0, 0xad, 0xa5, 0x98, 0x25, 0x62, 0x69, 0xba,
+    0xd7, 0xab, 0x78, 0x59, 0xd0,
+};
+
 // kNullPassword is a PKCS#8 encrypted private key using the null password.
 static const uint8_t kNullPassword[] = {
     0x30, 0x81, 0xb0, 0x30, 0x1b, 0x06, 0x0a, 0x2a, 0x86, 0x48, 0x86, 0xf7,
@@ -218,7 +268,10 @@
 }
 
 TEST(PKCS8Test, DecryptString) {
-  TestDecrypt(kDER, sizeof(kDER), "testing");
+  TestDecrypt(kEncryptedPBES2WithDESAndSHA1,
+              sizeof(kEncryptedPBES2WithDESAndSHA1), "testing");
+  TestDecrypt(kEncryptedPBES2WithAESAndSHA256,
+              sizeof(kEncryptedPBES2WithAESAndSHA256), "testing");
 }
 
 TEST(PKCS8Test, DecryptNull) {
diff --git a/src/crypto/pkcs8/pkcs8_x509.c b/src/crypto/pkcs8/pkcs8_x509.c
index 2c7841e..4458b56 100644
--- a/src/crypto/pkcs8/pkcs8_x509.c
+++ b/src/crypto/pkcs8/pkcs8_x509.c
@@ -293,6 +293,10 @@
   return ret;
 }
 
+// 1.2.840.113549.1.12.10.1.1
+static const uint8_t kKeyBag[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+                                  0x01, 0x0c, 0x0a, 0x01, 0x01};
+
 // 1.2.840.113549.1.12.10.1.2
 static const uint8_t kPKCS8ShroudedKeyBag[] = {
     0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x02};
@@ -392,16 +396,20 @@
     return 0;
   }
 
-  if (CBS_mem_equal(&bag_id, kPKCS8ShroudedKeyBag,
-                    sizeof(kPKCS8ShroudedKeyBag))) {
-    // See RFC 7292, section 4.2.2.
+  const int is_key_bag = CBS_mem_equal(&bag_id, kKeyBag, sizeof(kKeyBag));
+  const int is_shrouded_key_bag = CBS_mem_equal(&bag_id, kPKCS8ShroudedKeyBag,
+                                                sizeof(kPKCS8ShroudedKeyBag));
+  if (is_key_bag || is_shrouded_key_bag) {
+    // See RFC 7292, section 4.2.1 and 4.2.2.
     if (*ctx->out_key) {
       OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_MULTIPLE_PRIVATE_KEYS_IN_PKCS12);
       return 0;
     }
 
-    EVP_PKEY *pkey = PKCS8_parse_encrypted_private_key(
-        &wrapped_value, ctx->password, ctx->password_len);
+    EVP_PKEY *pkey =
+        is_key_bag ? EVP_parse_private_key(&wrapped_value)
+                   : PKCS8_parse_encrypted_private_key(
+                         &wrapped_value, ctx->password, ctx->password_len);
     if (pkey == NULL) {
       return 0;
     }
@@ -902,9 +910,25 @@
     return 0;
   }
 
+  // OpenSSL selects the last certificate which matches the private key as
+  // |out_cert|.
+  //
+  // TODO(davidben): OpenSSL additionally reverses the order of the
+  // certificates, which was likely originally a bug, but may be a feature by
+  // now. See https://crbug.com/boringssl/250 and
+  // https://github.com/openssl/openssl/issues/6698.
   *out_cert = NULL;
-  if (sk_X509_num(ca_certs) > 0) {
-    *out_cert = sk_X509_shift(ca_certs);
+  size_t num_certs = sk_X509_num(ca_certs);
+  if (*out_pkey != NULL && num_certs > 0) {
+    for (size_t i = num_certs - 1; i < num_certs; i--) {
+      X509 *cert = sk_X509_value(ca_certs, i);
+      if (X509_check_private_key(cert, *out_pkey)) {
+        *out_cert = cert;
+        sk_X509_delete(ca_certs, i);
+        break;
+      }
+      ERR_clear_error();
+    }
   }
 
   if (out_ca_certs) {
diff --git a/src/crypto/siphash/siphash.c b/src/crypto/siphash/siphash.c
new file mode 100644
index 0000000..7e4e9c5
--- /dev/null
+++ b/src/crypto/siphash/siphash.c
@@ -0,0 +1,80 @@
+/* Copyright (c) 2019, 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. */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <openssl/siphash.h>
+
+
+static void siphash_round(uint64_t v[4]) {
+  v[0] += v[1];
+  v[2] += v[3];
+  v[1] = (v[1] << 13) | (v[1] >> (64 - 13));
+  v[3] = (v[3] << 16) | (v[3] >> (64 - 16));
+  v[1] ^= v[0];
+  v[3] ^= v[2];
+  v[0] = (v[0] << 32) | (v[0] >> 32);
+  v[2] += v[1];
+  v[0] += v[3];
+  v[1] = (v[1] << 17) | (v[1] >> (64 - 17));
+  v[3] = (v[3] << 21) | (v[3] >> (64 - 21));
+  v[1] ^= v[2];
+  v[3] ^= v[0];
+  v[2] = (v[2] << 32) | (v[2] >> 32);
+}
+
+uint64_t SIPHASH_24(const uint64_t key[2], const uint8_t *input,
+                    size_t input_len) {
+  const size_t orig_input_len = input_len;
+
+  uint64_t v[4];
+  v[0] = key[0] ^ UINT64_C(0x736f6d6570736575);
+  v[1] = key[1] ^ UINT64_C(0x646f72616e646f6d);
+  v[2] = key[0] ^ UINT64_C(0x6c7967656e657261);
+  v[3] = key[1] ^ UINT64_C(0x7465646279746573);
+
+  while (input_len >= sizeof(uint64_t)) {
+    uint64_t m;
+    memcpy(&m, input, sizeof(m));
+    v[3] ^= m;
+    siphash_round(v);
+    siphash_round(v);
+    v[0] ^= m;
+
+    input += sizeof(uint64_t);
+    input_len -= sizeof(uint64_t);
+  }
+
+  union {
+    uint8_t bytes[8];
+    uint64_t word;
+  } last_block;
+  last_block.word = 0;
+  memcpy(last_block.bytes, input, input_len);
+  last_block.bytes[7] = orig_input_len & 0xff;
+
+  v[3] ^= last_block.word;
+  siphash_round(v);
+  siphash_round(v);
+  v[0] ^= last_block.word;
+
+  v[2] ^= 0xff;
+  siphash_round(v);
+  siphash_round(v);
+  siphash_round(v);
+  siphash_round(v);
+
+  return v[0] ^ v[1] ^ v[2] ^ v[3];
+}
diff --git a/src/crypto/siphash/siphash_test.cc b/src/crypto/siphash/siphash_test.cc
new file mode 100644
index 0000000..6d8f9e7
--- /dev/null
+++ b/src/crypto/siphash/siphash_test.cc
@@ -0,0 +1,59 @@
+/* Copyright (c) 2019, 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. */
+
+#include <stdint.h>
+
+#include <gtest/gtest.h>
+
+#include <openssl/siphash.h>
+
+#include "../test/file_test.h"
+#include "../test/test_util.h"
+
+TEST(SipHash, Basic) {
+  // This is the example from appendix A of the SipHash paper.
+  union {
+    uint8_t bytes[16];
+    uint64_t words[2];
+  } key;
+
+  for (unsigned i = 0; i < 16; i++) {
+    key.bytes[i] = i;
+  }
+
+  uint8_t input[15];
+  for (unsigned i = 0; i < sizeof(input); i++) {
+    input[i] = i;
+  }
+
+  EXPECT_EQ(UINT64_C(0xa129ca6149be45e5),
+            SIPHASH_24(key.words, input, sizeof(input)));
+}
+
+TEST(SipHash, Vectors) {
+  FileTestGTest("crypto/siphash/siphash_tests.txt", [](FileTest *t) {
+    std::vector<uint8_t> key, msg, hash;
+    ASSERT_TRUE(t->GetBytes(&key, "KEY"));
+    ASSERT_TRUE(t->GetBytes(&msg, "IN"));
+    ASSERT_TRUE(t->GetBytes(&hash, "HASH"));
+    ASSERT_EQ(16u, key.size());
+    ASSERT_EQ(8u, hash.size());
+
+    uint64_t key_words[2];
+    memcpy(key_words, key.data(), key.size());
+    uint64_t result = SIPHASH_24(key_words, msg.data(), msg.size());
+    EXPECT_EQ(Bytes(reinterpret_cast<uint8_t *>(&result), sizeof(result)),
+              Bytes(hash));
+  });
+}
diff --git a/src/crypto/siphash/siphash_tests.txt b/src/crypto/siphash/siphash_tests.txt
new file mode 100644
index 0000000..ce45c30
--- /dev/null
+++ b/src/crypto/siphash/siphash_tests.txt
@@ -0,0 +1,1081 @@
+# Random test vectors generated from another implementation of SipHash-2-4.
+
+KEY: 5a174c22c487d0c5c1161e570d10d145
+IN:
+HASH: 9f06a76d8ae7ff05
+
+KEY: d56745469ae42734c2ad87e7c13ea101
+IN: 3c
+HASH: 9698cd8e2620362f
+
+KEY: cc7c78ae24ca106c5d771742b530cbce
+IN: 6cfb
+HASH: a94c8f359a6034d0
+
+KEY: c8de6d120462739b29b25913f75f8be4
+IN: 8bff10
+HASH: 5f12329867deb530
+
+KEY: c435d95c06085dc26060e488f0691013
+IN: f575791b
+HASH: 38824fa193583e1c
+
+KEY: 5d5853675bbe37b0034e019c8703bbea
+IN: 20be54d583
+HASH: 5586f5ed0a52d560
+
+KEY: 77fdc2467cb5cfadba4dcb02f08d5de0
+IN: 58ff9d2af71e
+HASH: acc2bf8eb5308b5b
+
+KEY: 3f1abc8a74470e697aade7bb409d8c36
+IN: 2a0d12d99b8c10
+HASH: 2349992cbb601e73
+
+KEY: ad977640b6902db5fb05c35bde85e221
+IN: 336ddbba25fc15fd
+HASH: c7a610c0743b7f8c
+
+KEY: 9eeb5f0ce1545597c93fcd236e022df9
+IN: 123569d0a53d5399cf
+HASH: 7f25736f962974ce
+
+KEY: 56d9ecda6345127adbf79e4ec0871116
+IN: b472f565c1370a620c66
+HASH: 3f3acf72be80cb20
+
+KEY: 4b5a67fad0b4c6dd41656e79e47e4ccc
+IN: 317e7ef64c39cf111212cd
+HASH: 4c8ee337c7103c0b
+
+KEY: e0e04322fb57cf43fb6809a33d392565
+IN: a783aca573cb07e82829eb47
+HASH: 3d5ca090c68d3a94
+
+KEY: 2895c38788e064541584f222900a45fb
+IN: 6381894b7947f3cdb6eca1e8c5
+HASH: 8ee7682323bbf06b
+
+KEY: c3a0b4ea71d4e824459acac0aa2caa9d
+IN: 6a9257065f991f36ba175ec177b8
+HASH: 078b76ac3dc840e2
+
+KEY: 56e7c5dc7ba9c37095117d3b78b6be0c
+IN: d9a8402bc9303b5a67c3aa0f1975ac
+HASH: f5fc32c55915e9ff
+
+KEY: ffcd0ca31dbb4c0a64bce83d4a876e2f
+IN: 4c50fca688dcc34a62bf77554af22fca
+HASH: 9e266efdc2b55efa
+
+KEY: a358acb475fe3a545aba172e6a2e606b
+IN: e2e6007adac976736a222ad2a607d4d15d
+HASH: c4bb7312a7d39486
+
+KEY: cc611d043067a99cb5572bb0121beac8
+IN: 7428cfc5fd76f0ffc59a216b142576d9ee5d
+HASH: 67dc4aa8a52069df
+
+KEY: 19c44128e61c992e625a1187036566b5
+IN: f6b8f9422f6eda5c70999737573142bd0d503f
+HASH: 35d6199bb8b26627
+
+KEY: cc690d64cbbd6766af28d5e8809804e2
+IN: 80b9e15766ecf0fd988658701328a266220528f9
+HASH: 8da47f7d2c8a24b9
+
+KEY: d097c32707d111358a8f6470c4bbd3bb
+IN: 4a174edf086a5406f36aa20f1dc5f854df264ec159
+HASH: 8dab02709978b647
+
+KEY: 79a047071edd76cc634f9510dd0113c7
+IN: 247ba72455b9a97dabab6905b0a1227635f79f3e530e
+HASH: 1364c8043fb9baac
+
+KEY: b3092acdd6023215701aa9c99ae4e218
+IN: ce36b755300273c22805eebdfe7dadb59fff0bf8167fba
+HASH: c8e50f39c0605ecb
+
+KEY: db6c7ce746a0431f8269407f8c35a4cd
+IN: 40dbedc0f4c351a6ffc058a2ff63794419b1c2ecb4759f6d
+HASH: 9f46bd8f53a03217
+
+KEY: 110301071f6676fc2ae41a5cea83b10f
+IN: 7947c7e55c5fe0fd584ade5a30f8af69cfbcc4825ca4e45fd8
+HASH: 7305e77b0e3dd8bb
+
+KEY: 976c2ee47783766ea8235001f65d256b
+IN: 912be0ef8e0e7becfa176ed4006ee8f2e77107f022e42351cc20
+HASH: 2f05ddca521adcf1
+
+KEY: 58b1de6e33ffea228e144b4f25e5762e
+IN: 02e8b89312479f1b0a6da0f7f35e6ccb50ade322e70ba2f5b0a354
+HASH: dd0547e80e8505e1
+
+KEY: bc57f28c8f461d9714b48c087164a695
+IN: 990ceb64628bbafa3c0c4bdabf465a19d075fb66918dd240ebef176f
+HASH: 7ce492853fc22eb8
+
+KEY: 16c2ffc4e03d5f17064f22fe0512eebc
+IN: 42e78f029eaaa1474268d534d1542e6019af7c311b70a3b80d89fc8394
+HASH: 5830bdb3f6cdbf80
+
+KEY: 546cec4ec1ccdd1dec16ffd684fba160
+IN: 36fa97e2d20f91a56389c84d3c2670c25afd2d551de8930c735f30dc0f8e
+HASH: cc29ae6a40051fa9
+
+KEY: dc04255c1214077c60ed57c00719c4ae
+IN: f6eddb62225c75e47636587d992abb31fb1429cf0e4bdf7ca7610833c6c145
+HASH: 8959c817d89057b1
+
+KEY: 66fa7b74c5d112044e6e26a8f056672e
+IN: 9c303c5097358b4671f2b282d2286ccb9925e22717f3e1bb16a2a7f088c89efb
+HASH: ef44eb07b3dabf20
+
+KEY: e68c0b8b0fc194402a074069fa79bcc9
+IN: 68740dd32b54ab57313b7aba7212eeaeedcd00fc34b5a295c3742361acb450c331
+HASH: 31c38d85506f95e2
+
+KEY: 5a9bda075c2c3756bb03637b833a2c7c
+IN: 0a20ae05077a2775accf3e93197c2cbe58ec0c8cb2d666a6d1c5ab8f6a88921b33a6
+HASH: 83ac7417ef2f78cc
+
+KEY: bddb529beb2c89ee5f3e6b91c786f7be
+IN: a963b7e8a6ab9ebce913d39af035540d1172488c29b85217edf02143c23183da52a3ba
+HASH: 87bfacf3844aa239
+
+KEY: 65bf6deca182d55e94ebbf8fe0df1e01
+IN: 7c95a702ffec11b19ddafcb53feb2571214b06219feae9b8a058509fde4b54e1f16cb89b
+HASH: 170b6b574e480859
+
+KEY: 7ef0aa07183687c13f800f268a00ea86
+IN: 8c2b513a5facec6f5a34f49eedcdcda7bccf99fd9de0526ceff7c81ee11797d451d62cb689
+HASH: 5d0f521bc4b1c1e1
+
+KEY: 490cc83d84a0102cd83c11f8123733d2
+IN: 1f7312100fe65f082abecc08f276ff81164b21ad7ea83d7729dfd1ef622b5c39c884b3f82e49
+HASH: 2b72bb41af9f002a
+
+KEY: 81e1981c81e5c3010ca4b048eea1cc72
+IN: 012273854b89fb3c66e4427d460e8493927d71f44ba7ed7dc2b3cd3178faeb6f0471bc31436a85
+HASH: 6d67485ebe1e15ad
+
+KEY: b47f522fa34143dbccffc3b1f4093b09
+IN: ba235ce1ad78f2566f78270021fe9f31417c3f65f1533c43ef71d6d281d17722ae4bc8eb87636c2c
+HASH: 390e3fe50119cac3
+
+KEY: 9a3fedb06d1e550dbbb4c4c6c42c7b58
+IN: 95f641d66e88414ee49060d1b7bbb2d62f326eaf6cc3c77a359ea2dbcb0526f737b4a1797e7026d813
+HASH: a58b6f3f1df212fb
+
+KEY: 9111abb078f38c77eed9ff96e25ec5ea
+IN: e1476ccebc8fd7a5f5d1b944bd488bafa08caa713795f87e0364227b473b1cd5d83d0c72ce4ebab3e187
+HASH: 30527d54ef667563
+
+KEY: 42808b700fca9f85fddfb5d590807f0a
+IN: 5f81bd275320d97416e5e50d5d185d5542a157778b2d05521f27805b925e4f187d06829a2efd407ba11691
+HASH: e58617ceafeab62f
+
+KEY: b609075989500f06abed2bfc45c83d13
+IN: dfedfc41d8a98c617adab43f94b8d55a4fb1c02bd9c4939f8517e7207d3b8227d2bb8af086dc37e2ac24f437
+HASH: 0037588ae129b9e4
+
+KEY: c9afb75085e1759dc2bb6790ae29ff1d
+IN: 01a06ae550215331ad34fc87c2e9597d2a369753009c5a5fa2044481c6126bbbeab1a9f3d49f6198565fc6db9b
+HASH: 236763204e7ce6be
+
+KEY: ca4a9f84ef63e8b62514d34f2d74b4b6
+IN: 5f670e9e199c590700b7a7fd6b777f325ceb90f05b611ab77a970dda67db8636ba82bfc8770f742a22e66d39a59c
+HASH: bb3bd93cf471803a
+
+KEY: bbb3452a0a57a75460a77f238bdde048
+IN: 6279c2ee80f83cfc3074f24e5026a3fdbbc29e95f46a4e2862af2dda8e0c49b896f8186e0a3aa527dc4fad02a7c21b
+HASH: 823931f402c9d3ae
+
+KEY: c1f2268a501653c08ce64a34b1eda186
+IN: 2b55a854ed9125c148e97ed0fd128ab7a48bd0abf150aa86f60292feb3cb02da159698a01adc48bea1bd38a8f0339496
+HASH: 050afb47067c73d4
+
+KEY: b4a12074ce0969ae5de1cbd25e4f6f5d
+IN: a1543098dc8e85c7319269c590513eafae8bc5bec292b7718ea49018e3a0ce80843ce1aa644732eb083cfdb418582a4a7f
+HASH: 7c0fb55d3f5e8363
+
+KEY: 598233eb95280098c909e3dd3f93dcc7
+IN: af6e57e39dbfe09b91e57c212977b9b5ef48d30bf8bc8764e3796b5aa82680fb590d7ec73f4ef2357c34aaf2e12b45dd1f47
+HASH: 78ab42112974448c
+
+KEY: a91a39cfbfd3a01ba163294d96d99477
+IN: 092c683817c7484996b32e6c1fb46f3754b829b1b46120bfe1bb9123d139d46f5565c8dbc11a60273bf6b8e1bc38bc9a6f1995
+HASH: 7fa74c95642f9644
+
+KEY: 0b564ed2eee9e3649ccf53fe50d8b1c6
+IN: 05f42bd7cc89c80fb6eb8d09d4f4968fc47aabc0db6ebdbdf70a416815622d6e92de69cc675671db24d021be0b6c7545aab0becd
+HASH: 708bebc79de07dc6
+
+KEY: bae7c48ecb04834a577ad26c87022cd3
+IN: 9f5af8c66983938b16fe3a2de4d59faef425ca769d3a31c62330aafa1aba57e53bfc3d61357b618f1ae01bba3efecc65a70edcc8b8
+HASH: e9d43d98f790f1e9
+
+KEY: ac86394cf9e8668900fc3e731e5573d2
+IN: b832f6c610c6f3bc5ae50b656709f26777a9cfa6266faa80d788376d7a4b0af3c84e2f14a4b538bcde23e45f0554e7a333a95224b474
+HASH: 689de641675e6b3c
+
+KEY: dd60e6ec34522428fe517767fd94a5cf
+IN: 3ed67357449c3b24b6d8a381d92a443d333532aafd1c90411a29b80ffb6566cb13762c5c8c8ef87741023ffafe8ca473f77934acbd25c6
+HASH: 30ff83beb6a4eb30
+
+KEY: 9f5c48c10551808854f2c5965f2b8402
+IN: 9b160f17162a71a9aebbca3a32939cf09ea4aaaeb98c75aa5fe0c15a8a94cdbfb7716ea37b7f0aaa9e058d93ef5800e9ec863a5df85d51ff
+HASH: 987b4b16546765bc
+
+KEY: df955ec1e84432fec581bc446b10ba8a
+IN: 44ae821f9ddb1e1bfbec2259fad2042558fb216dee7c56af074d24b94a61074f37a011a46fc7542907af3a5c03ad64b34f1940219e3c129b8f
+HASH: ed3e957005bf498a
+
+KEY: 5a901ece66329a9488bd17ad3f350ec9
+IN: 3e404145993bf0c296c97729f9f2e6eb3bf22010fe642312c8136c0da176ed4c314eaee878047cfe0705a835a8a22e7d2b29c9328370032d4824
+HASH: b5e479243d036ead
+
+KEY: 6835a439c331ce630ef771f866f045a5
+IN: 5f2d95f898406fce05b36d1cfe21c57541bcbbb9293c3dd56e6fa8519e1ee76b40c2db8097ed008e84dd47b8aaae3c2b33037f9f7af38f3f41c9c4
+HASH: 7ce56ef8daac6676
+
+KEY: 84b224c92018a348dba300d4e17cd139
+IN: 9c520b111bb008086c5815f450a6b7b6daec0925c4b0c8cf99f9f9ddb6198000a379fcb62527d7c361ccbda2597deecdd055850abc6a17251c08577b
+HASH: 3e5ab339d1f90e72
+
+KEY: b3af10ef15d3e728b36171cd7e0bfc54
+IN: b0c30990fa7d8451403c84c7cbd650847dab3e087fdf2985eda79c48deda583bc9c4957e24b0502ab6004a85bbaaba74efe9bdf2377043d008ae14e169
+HASH: 0821d1f9e241ca1d
+
+KEY: 3f0dfe713054af061ab05dec911b8895
+IN: 0d3ee8fcd134e9814641fdff20b22ddf17ffcf3f23af7327e203cf1971329f92e99622d1b8329f9a8f9244c5efbd4ce3e07f1b9779f1d84927e8fb16c030
+HASH: 0a5fe889f9475d8e
+
+KEY: 62663e655ae3a122b869b11182f16a11
+IN: 82696187d910792ac92d50900677a1a0238d91cee3cd72ad949b50c53a0613add3bf0ab02c78e87f96847d5bda2bb31e4e19d92c933b1637aa00be18eec696
+HASH: a2c05353cb689240
+
+KEY: 1d3f1977fc1aaa27f459cb4de22a736e
+IN: 935e1cd9a08b1d0b57dbbc640915c6de3eec62f481cc64a27cda6a08db9e7dfa58d13dedad1ce2abfa967f059185b41f2b72114a4ba51a9a5d279f067ed9fba6
+HASH: f64a0fe86b7dffa1
+
+KEY: 96bf9034f02772a5150b0f2dfdd49c88
+IN: 2315e242205287d3e9dc5cbe317ddf3f286ad02fc4385c82bfdbd43ff6d5f425347e229faf0521acf9bbf3eb6f3abc2029c7af2506972444425e1b92aa1d6601ea
+HASH: 2ceb3eded2754829
+
+KEY: 5e2d0b77b3478b1d041b9b6784bd4e0c
+IN: f10b956532deea1838bebcb192cf256817525cb95242e5295830db8cee586e5cf3fdd0d9a5277d5a50a8dc6e4878d2cc6a549eb52bdc5beeb89ce870e65a87702eee
+HASH: 8fc78a2ca1ba1f7f
+
+KEY: b2c34273ff91123facb2e3f4ac03952d
+IN: a43e581d35caf54ece5b668df2f4a77e29bee3e1fe26add027b07e814991bc538da16f1649886e42be0a5be8b221ad155eb7489e81330ee91b194904086f91e9e71a78
+HASH: 36ea4accc3181075
+
+KEY: 828238257380186239aad56fda379060
+IN: 46f7fce30d03f04bee6559ace020e6ce72379001c20e5fb30bc7a500ce91262e0d7af70d8be30c61fee623e67a5e46db55d1dec64bd4be6af45bfff65050800194ba175c
+HASH: 7a1a36cfdd778b12
+
+KEY: aa751839cf5a43613a3b686bb22f9e02
+IN: e0848a835142871c489c772d01ca0115f226d39f94efde92178a38f87fbb371d4791f13954feb4f493bc0707c4dca732e24642cc6effa26da527ee7472c1c34c4b0b4834a2
+HASH: e144d10851a7e0da
+
+KEY: e3d027fcfc629cc735fdb70912799363
+IN: 08a51183fb0396a1633bf7a2c6ce4abd1e44d9153d7bc2a269f478269181df5ff29346366cea689a8301efca949693e1836d27d9cff181099e878b2bd53da75866f4abe0b64f
+HASH: 4a935a091f380fbe
+
+KEY: c9852a9cdd185cb16fd88ef793bdf598
+IN: 49cb5ab1bbb45afb32878b059685a40016f3add53623e23859c9384641c537e13aff631d814deed607bb6abc375c855f98744e455e937ee1c9e478c4878854166c30d0b686328b
+HASH: f2df3a49621a40ad
+
+KEY: a713bd5752e392d99947a0a0fef98da9
+IN: 15064ba012aa36887257f3f84261ac66134c36dc02d4d8688e2fb10ea36974c4fd3963144f8ff01e3132d2c69e5b57cdba82f98453238a51653facc718467ca781e73044aa368879
+HASH: f187bacabe914603
+
+KEY: 5292ed7c7317c2aa71831d03b905d0c2
+IN: e3eea270140e52ee204c78b9a4d86c0be9c74127acb4f3957b1fd380417dbd57f91fe69509126e531a62144cf7ec147b36aee7931b883a028f93993bb8068552e1ac9736775038c8cf
+HASH: a9a8c0bc50104e8d
+
+KEY: c3117a19ed788199add874b10f62190c
+IN: 112a91dad631502ec6d9a569d14cc009779bcf2ffa489ea4df85c275ec37148b25a133dcb6d4c3dbea8ed4375e7cf26885b991f96ad984880bff76a62568fc115d3762dccb4522f1a27c
+HASH: 4fc2a87b168477a8
+
+KEY: 45862eabc24e2b62acfd8833595b329c
+IN: 921a7d233720992b5d767b61f86b4d6857caa99d39e18656de8c5571f2a7295af7e0d703467e4c0b83f6f7079c63b55657776d3020424443756388ea217c2407284fc8657d3935a9b34573
+HASH: c1cc6da484446857
+
+KEY: d185262e194717d27be06c1297b8f27b
+IN: 7804f030a6d67847f53cc93052953516f66c915affba735df79df7da6d70bd511e4edb33fd712b58bfdff47d98b8acfaff064bd4e1f64828d61a82a5e72ad97573f4631fcec0ebfcbd16e67e
+HASH: f9d37ca5a7750584
+
+KEY: 53ea30bf7b1aeba84ae8e0b434a8657e
+IN: 65ffe0a087ce955a6170462711dd53fc057aeee9fb5e7a8cd866527fdce70aa4b8acc65ee4a366cdae649cb5342120cd7cbb9d536b1697cc45326a44494aedefb4f7d96fa5d0ddd104bdc6a84c
+HASH: 965ba9830abddee5
+
+KEY: 79bad8d7ff51922ef1d540b7d8d7663d
+IN: ed536d02d21049bfe6c1428ebb6a8faa481321a4977a685409b0fb2ca39d72c92d3db0ac406d56e2f15d6b6b62c73246807215bae613d283b8a35678df263fdbb6d3172c16909deb8e97d78694f3
+HASH: 4841b9dd5502f605
+
+KEY: 104104e76e645e83c7b40c6674906d55
+IN: a7546cb4e6b1b1c112aa28563028e910915c6b44b668bb57bd6623b941af4c3e22c7cf6eff2f6c474657fc5ee293db60a84944bafed2acabdbe6a6e7f0804ebed61786c2cf29cdfb0c62e8d41df81b
+HASH: d21db84542deb383
+
+KEY: 8d3c174a295a9d859e009f73e113403e
+IN: 16ed05c2b7b0c54df274bb67804c5d7671b915d899e15b2e166c3f1e4d9e990ab5be59c5e9fd70e1967a8021797ed8b2e40182f860dab2d0208dcc7c9fdb7bdcaabbc81f5b9b8751c558c5418dd654b2
+HASH: 0c4d09f8827c0c37
+
+KEY: 55bc19da3d5e60a3308379d5900165d9
+IN: d18ece72cd2f5fafe52fd55def2e6af0370e420d7aff7968f9cb5efc44ebb161a6bdd22132aad0aa9685e9168ebb9578209e51865625a4cc86c8f7898ad629b64e400a7d4687a5c3c000ad724a281440f2
+HASH: 7c47369a6d8387ff
+
+KEY: 58a3165308ef3c5e2a7c05070085455e
+IN: d9382554d5b633afa16065e6dbc0d472ee26629e6217a293b63af73cbd4a5dc3cf61ef7ebd5b6900a34bf4ade833c0dab9afe9d2d97369bf2814d03349d6e917adb5e0e1388772fad1a4baf64d7836fe3ffc
+HASH: 2ba3e3082bdd312b
+
+KEY: b636290e491b0ac22476c91c958ab313
+IN: 3d27f5c20db62e3abe1068c018fe8e09e4823b8c1209b74a50dbc19302e190350dba1c5ad616007c72be04bc21d7ef0a82804f266ba95fb3047a6a05de3f0d7ca3cc7c4633a02dda4f367051685535d8e1a155
+HASH: d14da7a2dc3b015c
+
+KEY: 667094c06354337178e646e23e453403
+IN: 215f856c43336295b5e4d625d8cf5b37c4a5b07a39eff2b42427d5df683982ae78ec85ba085740a28446c7928bce24be1b66a087898634a3f6260a3926c9a8f953e235e469c4de23fd32dde181168525b716a2cb
+HASH: 2f7cb69c5e4f942d
+
+KEY: 52eebf5170c603985982122bd8eeb1d6
+IN: da1ac8ccb4c66979633092524e42ca05df667ecc3921849a24c8fbf6aee70a01504ee2a80a000ffbc7b7629843f15270982a59ff9f3f081963a109ae8eee0a1d59baac5207f44071e51c64535b6920bc07bbc9048b
+HASH: d3a5fe4ac656bef1
+
+KEY: 1c1515cae3eba8cc9ed94c10f8a1c211
+IN: 4348d387039f7fe59a1d94a9e6d894241d62eb3ee91c19ebb09064792abb126710a619073fd293b59fecae7d90655d651e542be56a01ae5d419f14b1cee06cd90ee68e0106cd48130dd6e0d73db8fedbad3b00ada87e
+HASH: 57a4daa6abfb5a2d
+
+KEY: 8a8680122ba96ad89992741a8802f79e
+IN: 6ae10dfa31e5d06aeed3bf640591b9810245ccc175a8ff9f36893ad6e10c6dd9347d001f37c41123e4ea16b86b08aa3498c75bf5b8702228fb654595fb930cea2bb9ec97ffe1fba10fdbb7e8b16a2495df30abdfe1fc4f
+HASH: fc353ccf54b8c294
+
+KEY: 40d3e4941056b2585e1ed3bbd3196b24
+IN: e1764c110b024e2c406a18d6a6cb1b2d8dc05f8cbf635d9c6b59f8c54f7cfa1c9022f719d28979dfd75f6a221687690a046404fb1204cb27ab6502f6f24ac6d9c272852acfccb2d51948ddb427950c95ae4045699ec002c1
+HASH: 47108a5f3f664b98
+
+KEY: aa377b72cf2c282946fb87e6f3f16fbe
+IN: 4bf7833eb1e828df6fc190ecb568e3e175307408c4f851532afe731986d05c2e0a6d21c40b0228af69a0cf4f30293ba119462f3f95200386c70a4c49ad7251bc797be0b504efd50c98c3099b1119772deb764ba799bed28d35
+HASH: 6afdff80d074b585
+
+KEY: 304b7a8d2825ab0b4c78a82b202b1942
+IN: 43094418f2f65b85acacf5b3190711332fd3fa7c1e471a49a7d5134ab7f22def6fa973cb135a0add46fa482e7e29976abff74c3bc33af797b46540c85eecca45b848b55d3e412970c8937fb97d2b61af68ff3f2876bf8cb72dc3
+HASH: 8f89c9b9a3b9bf62
+
+KEY: 1b54ae53fbea3567a9678350ae934303
+IN: 8d02b02800d9605227fa73cc920962339645c72b560ba8c266b0b9e94eb8be0c9748718ba9cc48f1afd12e8b458d745596b763c7e65cb8a0fcc3d937c56e04a6873ceeefd3a77f3c545eb957a9a7bfbefabd47dca867ea92203c9a
+HASH: 86f09b1edb5f7934
+
+KEY: ada9526398c0ffe64b200ec1823a0d16
+IN: f6fcdf72c13acae3886dbf6a842806fc9ad021d0329e595748a8cf82f375fd357e1cc37a7d995d869373cad1dd8ecb8dbf0e5333767470378fd7e5b0d1fefedc18fcf045cfac7d883e67a8a32efbccc1a87a8e089f34186f7ea4ac41
+HASH: 64e098ae04f9e06b
+
+KEY: 30f08a4322a8c71e1e1a6353b371fd4d
+IN: 80df14c6bb51846621cd95b57c02e34afe6a96eeba8bc29002b2514f26c3ce53dc81330ebdfe8bce32d4e789f5bc354b03b4d10a64a10248dd1626726ff607529386f7becf9d716664bed65656629cef7fc7482e9046af09aa7ed60072
+HASH: 746b10b00aafaf2c
+
+KEY: 9bfa27a589f425dd70390c6ca1e1760b
+IN: 79fc7caea4cf78001a8d601de3438584517972e81d55c8e00b5c8ffd1e1aa5896058a56e636a4e66842d31f5287e01587601a9f79488db6f28f0a5644b34de163831cf462493ec579ee0c7631adda09f5e135cd70e6a4504e52823c1cbc5
+HASH: 1899998766305ccb
+
+KEY: fab33b56c2b97f9899438d8c4448a721
+IN: fb7e625072c6dedcf31d0fbdaaac81585465f6227d1a37d60befeb9662823d2bfb70f0dca67af4c1c60f72a524fef0c243a758f8b2883f17f2b113277fd71d28378c027cc9aa8c79d5dadcd65c2ff275f29a428437f424ab2171c33f819df4
+HASH: 208be5482e6ebba2
+
+KEY: 6ede0e899a0dacffa8c8ae3cf7552310
+IN: 9865e6577b8811c937447194caf30ca9db318f3949a0a095a148fa0fe0fe7a0ef4efd7c04c7f0ac13206841ab8b30b6b1a55b1555da37a40d5abf1543d59f1331309c1ce5f2adb39259c152628dfbe10c0c5e81ab172c025f9b84abc2d996834
+HASH: e46838d2cbfe6c33
+
+KEY: 2f0b8714a8f0454f62db8dc4fa506a6b
+IN: 6c45eb8e88bf9e63db9d1558e381f4a6f831727d866daaff9d402ba1c3ec6911aa4c62dff1bfac2fb00a50be2c2d945b4c1cd10d1ab0d96d201a5e38d80a5ac2cbee6b2945091e91aa40ccff6f37656392758bbb7da5ce2ef3c933b1bf83f82201
+HASH: 433657c53e5fa081
+
+KEY: 85cf9b7e4bd9af87f8fff9d854fb894a
+IN: 0e808570f9bfa7fde488e3fedfd61905b232a2e6c512ff4659b6b03722d4d1a8ab0b757de0eb114c52620054364d51d6087417f2ecc73bfc78caa8dc5c3063722b8c7387f5cf1eea7369baf108092b1736f34f3c85a1a64caaf188753cc348ac0934
+HASH: 25272780593c358a
+
+KEY: 903764f0b9bcab23a0d1e7b0d8248711
+IN: e63b264edf7a3ab19e8f38ae22fb9e00ed7c80d25e3a9d76def3c3cc7975ed5a19aeb552a559da1cc6d6f4d7ad5ade44bd7bb57ad1910a469fc7082267ce9644e9d4e24f1ce5afe6cb3886172485df77b3e47dce36e549c62129788648c3738f836caf
+HASH: e995824b960f3012
+
+KEY: 902dea339b29a5a4c54c71ac3486b509
+IN: e33910d372dedb9a154cd8b1ffd4d98c58c4b8c93b5d7ff59a87a40730547ffb145a820aea56bca4f7c043c40aa2f5e0099b9a91683d2d2927febca53c727ecfd8dca77830012c0d33135a2d1a5665c013c4126acec9073cee84f429c2a1d57abff643aa
+HASH: 7a6d533f1d13f598
+
+KEY: d760372d5a2224bdd096ca9c317b315d
+IN: 868257422e5898c7de261682a088bf97a19bddba87650eaef967e4085d5145787dd8a2236859c67b1d96e64a81934210b63ee44adeb5cedae03cf3598496f7b77caa7f2a2f123778e3720229ee7eeef87837a0d5de7ced3baf40114d663bc0e576ce6cec70
+HASH: 05eeed619f335b0b
+
+KEY: d443db5067af5681f8b61a489400d3b3
+IN: 2f69ba36de9d7f2489ad092263f06e04b87179dbbdc3725a8478abf6930f19ebfd1a23823d41f851618f6d533ca56273e602918c27926cd77e7380de5af322bfa2704ec669ef402971bf606da799b85c8d51ea8e2f8b8df3e9e4ecd9cb03f9c1bc387fb69a02
+HASH: 3b54dfef0537f65a
+
+KEY: ff85c309dd953710c4c450015a92db7d
+IN: 3b4cc08fb998a83c0bcfda112957646e708a5e7f457c33a4e31fcfeba9fb5bc37be521bf351eb5ac1bf11144c3fd4837cf9d9728b02b75c51fef0342140e3a0d199fe0a67710a3a7131c6d3c33e0ca00259cab1b7cd1d6de7ef81e97dee7ecaba9e2073c08096f
+HASH: 0880d53fcd40525d
+
+KEY: 50703fdb1e055a0f72353a13de243741
+IN: e94047f4276abc898fa1e268b53ba5effecad744a315d93f1218a47725ac6512bbf0e418681cb15e6a98c3a82bb3fff9f64583ba39268a70a45d91f432baabf3f38335b1e9f52a4141bfaa1c55570ab0ecdfccd5d7cee5bbf692277849891625deb64a8850a477bc
+HASH: 758c1f2f92b68e49
+
+KEY: 2b022fdf4bb13146ba014e1f2abd73d3
+IN: af723b77807c45b4aaea43e9d79a9149fcd1fdc59849b1bffa88abf27c6a779fd8172f325909ae43e49c2a44d3991350a377d58643e5f6ca9d8743c7842d58cb8706814d783e2c7855b8d63e55164cbc1fd7a13bb963d6c5bf4be737a0159c1347a72b88c0c5115a4f
+HASH: 2275a762396e6942
+
+KEY: d6c230457b3494e373c113c4fbfc8fef
+IN: 7820e1b5f75780ae7e6360c6708ec8bb5f82f948e656deb214a29d7887162c67aa1dc547b9b1b878f870cb44b22c61f804c6a9bfefd38588253360532a558c4176c98b63872df3741a718d8487e9bf17d5da3ca9145cb76a9ace837fc9b1f3065b8a777e10fa95dcfbb3
+HASH: 53b8f6e69f5a89d7
+
+KEY: 17f7716d7d49fbabdf1583287dda7802
+IN: 96ab47e1979e30b418b98f8b0f86ee5f5a773b3ab1d062274a4335f2f8cec6a0586aeaae7dc4d0da82e52c3b4b670b0ccd724b0100af58ddc74899995bf000fc626490b19b8bbf4f6352879a0530e97f9bd2418104a1a27e7252df3a6b996b27f54503ef8a718a8123d580
+HASH: 8aedf4fc510a8fe7
+
+KEY: b12168e2e46319863bc595aa421cb4ba
+IN: 0ce0a170319192d56484fc847a8285abe28b3ba3f8aef58702df5bf50e4d0ad2320946d8256dbf2338c8c0a6a1da48496adf9941a4d38d74e0c8aa52dbb5c757e28fe3756421964a3e4eeb9fdecf2529d70216b00515d5869087b611ed0ca1607a650ed6a3707ec6bdfa4d5b
+HASH: 4d85e666f836edcd
+
+KEY: b05bd753656eae367d8f37b4907e4fa7
+IN: fa2a52f77554506d25e8847bfe613139565cedbce07110a7a5af53d024204af9e0bb08c8266616dfc1e21bd5c651de626e1303b08c0c90ab709613caf2713848ff1ba3310852a575d07e12c7691cb081157147a413e80d53a55248240eb5dc566b8dd67d616c5d4dd8199d5d69
+HASH: 885116def6316c20
+
+KEY: 0bb368a25169217504ce1bbce5820394
+IN: 34b4df9ec90072e91548fb40a35128349986b7e50e425d894664a270f919456abc031284b29b6bebd734e53d5241919a8dccb05c38c633fcbad53979d92eb7cfe111584155a4c352f06c29242178ed4d904ed739a04d4ebedd27b7043d79afa9b740df4ad58c5640da67eb121997
+HASH: fd965ec9c1ef4449
+
+KEY: fdaec5633f8d54859574792bcda87468
+IN: ed96b9cd567f55d1d9e087bd63abccd07baaacca8a6d375d2576f00b1e09b5d920200a74cb12e8721e550c80b8c61ac7a8e733e1a14ad0311c30afc83f5aa7a8cd9fca47aafa84babbef5f93a86eacb5d50f696b7a60f0ac48f9b57dcab2168ce26f6390ce3c091a947397db050ce5
+HASH: e92615340f232b12
+
+KEY: d529ce056cb054580b538c81752d63b5
+IN: 75677757aa8eec8e44411d238aac77fbf1158c1c9c3174b78eab4a70034c325e52cd1a275fc0a24c432f82a10b5920a922981dac43571500263c0bbfb95d08bf20acb526bc6a180e6519499cd571414f7bf69d704b476d62083d53049674c46fb616b028bd269e1b822daae40ae4ec31
+HASH: 9d6ee722d9af0ac1
+
+KEY: 28b413218335bcc2f9031dc3554b0585
+IN: e4ac05d0860d28f45b7dde35028dfa67c25c8364545bf27a016228c4f1b5cbdacc418806d229d1fd2d30570fdb1304e38c7443c02d2e55ddd20718dd09d0b3a667df71c2040f079c9798a7f5ca7b09186df6c44edf4740433ec17873180038f7c5e0d4220d3755e264b0543ad5a5c1d537
+HASH: 7d6d8be4026abd19
+
+KEY: ab31c8820d2d02f63dc3ac5acac3828b
+IN: dae9931233787d476b472cb8e7ca4f86c2f8b9cdc38f49a50857f99e4e64b89d451e1bc8e5afacae36579d51b8bf67a4be36a65f9b464a150c1fc012c115bec08e45bea214b80ad39e1a705cb9b0b759901133d6619812ded8abf7848a67758f483aacd11297315dc190746a44e9ed56708b
+HASH: c9cd54ada0139758
+
+KEY: 145d3281341d26c75cbffbaab06d783a
+IN: 9859eb836c49cb41beb29eb493988b459f5d0b22a4dac9e64c34f93a71a700612cd568f76b84cf71966edfebaf6b27ef52524208e4905b83f8d52e879618180101c1ea0e326a44bb4d59539f779a0157af835c5bd7009882f16bb2aa2c87d49b6db49e996390a3ace578794c4d0f1802dc0a82
+HASH: 219b0dfe6c21ff2a
+
+KEY: db21409aca53c06e34752eaf651ae7ec
+IN: 962ec5dbe61976d91eca580b50d3b21c478bb8139da94be0a90e68f89b26b8b28d3484cb938791cf729a269c54af470e528a72ac9122ea10c08e836f8f32f1c1bbcf10716ef243d2abbe31bbf52dfd6c6884b81d6c48f18f087842b055432e5f67de978a2e7bdcd645dec43feb33f2be7fc8bb5d
+HASH: b31faaa12c80d2b3
+
+KEY: 0a92a6dce6418f53cd3823b9a40c5183
+IN: d1f4d53bd0409a3127ef19482d47964197bc061bc779b32ca5ca6f546c18180dfabd32b5a8519f868ac3ab67cd72c3bfea3ad96f5e40fc09a64046ff818928ef87ed043d0a0994c1adb8b0bdf446188b98b20dc65ada689654c5fcf8fe26d66baba8c02d8b39ee2be9217c09a62a4fce8236eda6f7
+HASH: 3e9be4b24cd3f47f
+
+KEY: 868dfe7c81ff02bded7c42a242d4c1bd
+IN: dea44a7466a4910b89fe6abcd824e65603a17ac436890fecbf4f6bc6809be8b13c0a6d593840d48ac59de059787dab1c1ca09a6834114cbf7ff2686a71ec12921aff5e13e9cd80c77ba7adbd361e72429c2b46eccbe27b25068cf457f865bd0149da14383317bcf8fbf949e36eabc7f8dde08c5483a4
+HASH: a0b3afbcb9412ad0
+
+KEY: bc662a2870b8baf74a1153e5d24b4832
+IN: 503401e821133e2ec69942d28503993e0337ac43502466f788fde4821e5bb2be6b0aa80a6886eb10dceec3225762a1357992b740d5ada6f6acb26761c57532599c66c9b55812ced61bc0293781f600fa1b2d211a7556702f41203b7824654207894feb9e2f744e03f5d79682ba4570756050e42eecb3e0
+HASH: 285affa883d1597e
+
+KEY: a51217e323aaa13dc0debaa2d26a7141
+IN: 52c40d6b8dfd5fc99158481dd889643452c533643534298382d2f3d159a7c0f019cb614d8773299451ff87520d9680e5f283e9e9a2fe9a8a7b5e4bda6b94e578b97948eb9bd868ac3c33ba79df325c141eb83ad6be7c1b5c9001d59f88a4bb2208ac62dace5922b6df16f09092b48d432bcdd79b4eb484cc
+HASH: 5aae64a7fcaf3072
+
+KEY: 89c8b8541be48278ee4cafed6616a581
+IN: 86a7c752d913e379429724ff0358994fb8ccc6605573437ed5742fd1b2934c6943259c2eefc5000f5292901a154e856df3c8b7fdf370bc72ffae5957f104fb3b07ed448def575680bc637e3804ce9cca9ce1d85fb79b33e8e4c45f1f974d0613d40c63734b9c927fd0e6e16bb4d288204bb759b199269fa7cf
+HASH: 339b0843b05f2652
+
+KEY: e8b664ce32d35f060e5a015caa287b7f
+IN: c9d496f2e1c2141154bfeb0c4a4347fa7fa002ab2573c634b2c2a376fd270c7d45d2fd6078e4d997aed34bd4dbfb308e2c1a14d07f58e1363c3fc4bf32adf4af4a6d1bcb79e4c5c3db2ed2c68fd2229b6b5fc831d7a2cce989d2ef8dce13f9e076aeabb5a30c7ae72f4e98874f885cdb52518e71a020e0bfb7c9
+HASH: 254a78254aa531f7
+
+KEY: 1ce06b6d7f0c15734448e53a4fa52fb3
+IN: fda649ace37b9fe4f4d572d8b1b74e28ba13db46f6b0f42ba9ac940d379d6d50623fc1ef7545763651818347ce38a3d86828148b14ef090dea626c5c3d1379341704f719ffbb234e423b0024ee635ccd2ee50c501d8689f84c929d689745a05bba54f99a23fb248366ab05e31bfbc18be87110d419ac43834476f2
+HASH: e5d2f7f9315945d9
+
+KEY: 84c2f71ddfa145332dabe73460c7e03d
+IN: a8a162a8b39975a998568ede08dac2c98a7da7021711e159356c13cd39f5080fbfe7f9f2ffd75174ed6bcd82fe0cb158a83d10bd64639f0c6a10335635c4aab34bc0986b898b9b4dbc013fa456a34374aee1a0d437f1de697f5fd2b87c9ad39017434d66761c0f26e81e9c99a9dd57f390f2488ebbfe7d1666cfda65
+HASH: 7c416c3bb889ff8b
+
+KEY: 63ab163cdeda549ba268896b9a677d89
+IN: 64be9cb0aadb6544d4401ba45a3d2b7d766d7d67e09bb2f141e743fa72b8dd14d7427fa2ec37ca6dc5c32861962c7744b1afeea8f28f878420cf67268281ab9ad6dcf0aacdbb52a6983cb91ebfafd294aefce90c6f3628376d7afaf4fd5818602328e215cf1aa3c9510ce53f42dc04288accde985e90224260e318d589
+HASH: 21cb18940175042b
+
+KEY: 2f8057e7a18f356d187ac98e43127515
+IN: f5c76f7b6d6cf30342c1f4673ac063091c9c02862238549255bfc2530f743c69cf4943a9d4035fa1912826673b3c9892a489e0afc011abc45bb5a251f2c80ec97962f366376841939e9997637ee1a2dc0655e9e8a1d262b046cc4c0eccb981ffb46d494d5537cdb202b840601380212f67e728cf9c3867cbc53c0a825f61
+HASH: 843747a123971b2c
+
+KEY: 2e764495a9e9d8ce7dce2a634a374423
+IN: 7c7b09631d3e04123eed9efc8ffa34f3182e40ef700f2f0989627d667719a7004a4f0be4fd987400ee12216dd7ce4b57b889b8d5747e5c0c00dfd4eb9011bcd848070ad0d6dee431c50caf613125f9165ad05c69b4859f7ef55869892385cf2fe40dd3c4051a056980805e816e86e28493f7bfd16218e523b2c72ae478d0b8
+HASH: 65fb43b58c02946d
+
+KEY: 1f2aabaf8e9f8a8a428a564020d44b46
+IN: a2af9818cad478d6384f541cd1ed9dfb764f6953c641a6ce40ba2adcdca047eba021f5593b48d18bcbd6581f776614dfd3c6765fd22fc6e76baf6464d377b1b6607dc3329475c999f8f6683b22ab7f0ba1c8d0867656a0cb3840cc80c543acc598a5df3f14dd3d7de2554c837fbcbdeae84529170e5927c5a737c11b92d36470
+HASH: 256afb4a3c5c11bc
+
+KEY: 6613048242a353b7a908c3c1d137a289
+IN: cb24d4c11dbc1adc4f4b65a04b1e9ad37b5257d314c091b2ef870556cfdebb4ac2e4c1348ab5ba2047ff9d62e9b78d6b8a29858dd105549d943d2fbe4c96c78c07b864756a9445a0235cdec30c191e86912d83ee0bd1fa094431fe7cca2ae73e2b611b11f67883812914524f5872011d52514b340c6ff107ae2bd65a1792cd9875
+HASH: 6bc58dc448c8bfec
+
+KEY: a8496ceffa962e684541790ea6a43b6b
+IN: 96c08d385c2eb59b806b9ce99b82f28abbe3ee3dcd481073ce17301029454c7ddc303f84a019c99ba45b339e4f7a25852508c364fd6d6fd507d9e1d77a7e9f71f4df72ecf9d197a31d40b379c576a0dce84c7121c368cf8da052782059ad80f47b0b8cdc6e6c986aba78b6fe8171d830dd4540aa95e8b122961d9191182ff57453a7
+HASH: 56771e9dec77ba8a
+
+KEY: c97c5bcf612aad1b71528b6af8f5a160
+IN: 8761e3d8f78ed3f66ddf9733dc8db84e7c1a0c268e91b6c071d4570272d9bf6d40c76752a745b304ae09506a8b751b76663dc85e48fc52df8df8aa978d082ef1a527d55d3cabd2007eda751dd2589bc4d86b69ce8ec9d0e790ea486168a0fb621861c9c9d4aa76fd67217a9c1fc9eb329160208616d5f491c46fb5159a68bf376df2e1
+HASH: 3e3d191e902ffbfe
+
+KEY: 0300b9d764049d7976f2c54ac2b0ddf8
+IN: a95265b3049332c4de454f08c0ad3c2eddc62b01b6aa111a2900ebb4ab126a6739157d84895e96214cb5353653f880bcd04a6410277991baf42fe34aaf131c3c84a83b6c29899180b1f67b76d7b327a1816c4236ee9d6306fb986bedd21570ebf9efa8e676cb723f91ee14857c186c80725f35d9ad0da73b1e07a1b935c29fe08e6af0cc
+HASH: bf8fdf47f81bbb66
+
+KEY: f93caa15e37c8785f1f4d06a7a318f6f
+IN: 06c1ffceef712559ccf22ff74eb33df2dc03230886b3b76f54c8dad4a6129e5472e4e9491d2ca5fdaa9be2c5fcafe80f957b99d3e98537e67bb6742901fd442e06fa4cfc906f46dca20182f07fa6f92ac75ef53562468288eba37d6e449026c26f08cf49333e7056f9eb29694bb68b4cd2375e409c8b470a77ea9e207c10578cbb1069da88
+HASH: d8ef7ea4f58a0dc7
+
+KEY: 127bc3d3828482fd62dc36ec9a558edd
+IN: 98578efde5a93aeb470503b3400c28c513530c7cef901d7c335b30edeaab40b6cfe429d6ddbf4b32701bb5924c82fd8c683d77bb41ff056e3f325c107493b00b4cf5288d2b364027713aa4f0bf67b77356ca36b5a77f1ddce1ef04b8b7569ea816647849e6eda1d51073a076a9c7eb5b21a3f392b35d1930d0a90de0a28e5e14d4b90f054e16
+HASH: 2fd51c80624ffa26
+
+KEY: 938dbdca5b04ce705fcfa14f2829430b
+IN: 77b7e8762402e4feb7bbb5c68b44003ebefad025c844f62feebdd82b0f221e1efb92bf831e6d0bac80c97234a4f59593bf209f2169a410935c52215928c56d6ebb6cd2ea78b3e497feca2f90f9053607626fd8572f08158a2596247330ee57f58f57eaced51682848f7a979cf95c65b84e9b8d0daf8aa0987c51eaf5b887339c70f00099e65eab
+HASH: faecf6c8febda350
+
+KEY: 8aa112a940a8855fbaa384623b949a22
+IN: 77be78dab861a36e7f7511d0b10866b9b04da12c4d4ffb6126081e8ec26ddbf41bb72f883a1136bc0adec7d6b848472a0037316515a70ce899e3636cfcbb548799371a52dc68551c4636f9aaf358f294aa1ead08ef78c21afa2f5b93fbee8d92ca2829ef1fa1396cd6cab03ed18d85a6250fabfc7cd538afaf2a014ffef82a63646a0e2025db9c89
+HASH: fadfb08f99d573ed
+
+KEY: 3219a0421a7369f055c9d5f7766eb1c2
+IN: db47f97f5d4d49aee4e5145172818736158676bc1bf7bc075bf76aa97af680ef81d2e2a1fab5fa83bf4cdf88bd7aa7610483150ef7046414772157921d0d01a495f35c1f262f4679990a1dfb4839c3085cf716fe040216febbb974ae49650fc7e9b7c30e354bde856888beb4bcaf8d4b14aa4bce08ebdd742f3074aaa6e742c4864d48c94e5e6574e0
+HASH: 0034c746323cf711
+
+KEY: 7197a414a48b2ce83a992351965bd6c5
+IN: 1be7cbb6274e2b1e6b588c6d70fd1ef929bd9ab0649003a69fd7cadd2b9b9917cbc89a069ec9944455e944b4c1bb520bdb97740d9174e6f9eed4fca24a7253ebbf8cfb15e56a352eff8fda4b0823a6b52ea28f214af0caf568c750cb43c2f39219a57b930cf8f5151543194b0f8e68c78ad7a50b08b642333b6f3b674c4ae4879c8d3ed5c9ba7dc16455
+HASH: 89daef38d2d0b92b
+
+KEY: 57e780c523aee814bbf98f6359a23caa
+IN: 44908bdfa0d5fd1f5ba1bf253b20c16c3e2c5834da8e8fc7eeadd21835efcf45ab6d42447bee2530db303bd6821f1b1f1010afb192fe818574e292ac430ed5b6d712b9bccbd235ccef4dc618dc7192f4b80de5ce5088ec3aed940b80c84efd4c6f31ab9053da0f84e39dc9718401c0231f9e13ecfe4f4b731c8b24c6a8f9a9bfda9ececc2eec200ff742d0
+HASH: 5ae9f9dc3920c206
+
+KEY: b0d51d3ade35641688f258adea74e9d1
+IN: a522a571bdb8904928f1c4e79aa00e791cab00e426d94d0bfac5a9af48f340531fc0c256aa799240befbce757f314ff9a38ad1e994431e11b771687c06e0a345085a5bc3f95a76af6600f217cb66ff85f60b39c120a86ba012e86d43811347ff37b61908e65542a102747daca5d3558118d3ce2ca2dad1c38131df27e42fccbcfee78eed2f87f2bb250159e5
+HASH: 4bc688bb42425151
+
+KEY: 49980eb295bb6997555a0504ba4754f5
+IN: 31e9b2ac04a1cbaf9f7c03cf2d70f90f683bbc0eb2ddcddedea917d14414f7e90fa09be64ac18492e2d90d6977675976cd5abf0e0556284a5edd773d6e4af29b326cd2be4e16c7293ab0af09fccb9d16701e6db5a81d42ea0575531c51cb5ea076b7c957a94401da78d43076515343db0816fa20c38ec5d35413924c70fa0157b280b02dbf982614984427d950
+HASH: 1d70036369da4147
+
+KEY: 67a590f4b8c902865a25e380a81cd630
+IN: 35423f98b55467adf90e372f516483fc3ef1d72a001e190c7a29545c9c993267be0757fca45ba1495f65218702a5f617ec8237710aaf030dc9264afa9c7da8ccc16f2c6775ba1c65a1a91f4eaa4ff035508fcd4cae9faf3223e97f3d84ff0897025a25c687d58b2876395647d7489d1fd714688f64ff47f97720f4ec5b0becedecb3da35949b40d9bfedc35dba7a
+HASH: 92b8f8135b61c24e
+
+KEY: 5542c4636e7c3067df8a15c873bbce77
+IN: be24fea7363b5f0a72a850c79d709e3a990b416082c4c33fbb0b09c97649fa1c56d15abaf14cc3c2c34d2a9cfa30e67988b697b7d3362cb07c460555ebf1f5e1c03eb905c8d0262880aebaca524bb7edaf79bd3aaf888be6d11e511d7bf6db5b89b0a2d2c1e42baece57345055b6de9eb67c2befe8ced5b4e2184d46342f3f2ff24bb38899ab974d71d2548f50d21d
+HASH: 5236a88232eb5ab1
+
+KEY: 2147fb23cfb2f82fb93cfe405cc89f22
+IN: a65cf5b4dc2eb1973009838bb3815749f4f3644a43057ddfd61edd942c4192ede85787193bf707d6bad890ace02c94b4ee2348a159e61172305eccf50e7cf8559cff42fbaf7a100c234df85422d6d9c0b8699f25b58e991a1be64d537566c602fbe54980398c238a2e87425eb7c74adc6d2f76162412b358978ced512bf26e7507d671c393729a9ac3a07c7436dc9cba
+HASH: 2da3b7beed2821f4
+
+KEY: d429971af1d1f3ddc043dd56bea43ecc
+IN: 3a28e01bb245cb6435025bef500ddf08593d7b3c7a8be01651be6363dce1e7a3a59f49c0f9403b072664700e74f4599b92f21d57886d97013a24d1c6a49db7de84176be8eda33a373f347ea8664aab786b337ac1d88148bdb4dc456a92b2a36621dabf7107e39e4b499e931bdcd21a1949f0e9eedb716fa94752d2a565592523960700187aed35e490f45da2d7ba382258
+HASH: 838d68d92d6af47e
+
+KEY: fed60ab1233c909413a7f8de9a641c8e
+IN: d46c4556b5031479e01d40cdeae3adcd14fc6ea6e1515ce7c976ad6e62a97df03b786ff0e7a66604de5d935256e550853609aa6498af380d2ac609723f949f01662240c3925eeffd16793c1b589e25cbe6ba00bf437e4ac6c56d96db33b17bf54530480588adcb14d8b8eebf1e6a5dc796907239bca7fa2f43e1c51ceffbde22a61eed45aca9ec48f7ae11ea798c16b903ce
+HASH: 4cbc3c663ef52c50
+
+KEY: 2d2231ab61372d4b93421dddb351467a
+IN: cf9d2b12f0d61a9ed081b308fbac0ed639285ce379d6c37aa4acb97a675a13bd2a0acab0ca4f06e557aa670f7cf2208cd5e37981f4c09dd37ed4677cf8d2206f0b09aa6185d152263b8636ab1f439c39d33ff2e6ff433e3d19231f07853dc2b762f527be9eb3640af960743f2500731b7f4593bc4c6a322434dccbe808aab7f0936a1fde0c10d8aa0c856d782ffeacc3e2e768
+HASH: 832d6dbe4d2ec17e
+
+KEY: bb1310e68d324c65d8d1269d60acd39d
+IN: 8a8091da5940bf6ba16eef2ea3cc5d4728128e0684fd453f6527d330abc2ca8374004f1f10a54bfa4be8b39d4f77b6c89d481e0fee2604d402c1593dfb930e7da4d314c70d4312dc2051cd05f16680b5a5748f81a1a448fe4e51b9184e6515f3dc61a2ed6d3866d8aef1df5610c41a3515420c6d5ffa9a89f9b64a8a73f0275ac63d5c09e60e3446ee888869e42a8ba3b849dfb6
+HASH: 342bc4c512c02b30
+
+KEY: 20d58e0da885103776f5147b19bb948c
+IN: 02b2b75f81589f8948471cf477f22765729a7b75368e3955662cc3938e05f0c5a33ef9e99877a5bdf2ad758165d5ba9b3e6646eb0e53a07aae5abaf73654747a9163038ab35c1303e28320d26ef8aa56df8f3d83c10863c5364acbb8d4a11b8a881524876e7eedfe087a76d19ccb4af14ead8390327b5b878f93758a665fd330d7923c9e2d56a20fc8896bfe5a3533df613ea95717
+HASH: 2fe22c1ba3676005
+
+KEY: 2ec216c155f5e259a96fdcdb37f40028
+IN: fe8d46bdcfdb671a2eaedffea8c9a454a5556c41b0cc25d688ef101117707c14f9d1c1410c15e895bbd90170d3a2b910af074b40d8eb426c588a2121dc2d952a200c364a31dd919d7792645ac3e1534ca5cbceac7a95eeca04b5cffbac9b9f3b58e6a60d9d1f1bc7a961975b8f52532fa7ec0a9c7250cc383f6b772be31bfe86accb6d2cc9a32ae42b80dfd723a401ea668774d41086
+HASH: c0c1c1dd9b39ca84
+
+KEY: c43d26f4654e21ef41fa72e94bd6d887
+IN: 8425382252dacdba8ec398cc247232f3fae06c2efe5b19401c4671f2664bb4a97e5987e31ae32ee0004ec4c8d0abf10c35bff9e6052d490f1578054ceee87f9eb7d14e0511cc65bff05bff59893781b8a81d58ad072d8e84fb8295210e0ab19cb4b2acaf63e6fbe79b74bcdf569e35aa14c66897d98a9b548e65ddbf5e50f3360666bf3099b8bff1d7ffb9a14bf6ecd12227c504fd0b68
+HASH: 2333bacf5cfe1865
+
+KEY: 9b564f82baa0817b7d29c809f724f776
+IN: 1d3b1a79ae01ca377affeb064b11bcc87499c9b1d1ed9b953c80fd54ee703117705e673ed099d4578cb46f717bca43847c6e98c3784dd69a5298b4e93739d5e8f2c216cf0added49d4a0d9ffa38bfa9558a71b5c43edf6868b295cc020ca0bcaccabe0ac3f4d9d1c6af44e26d8fda776665d2fe26ef1f3b45fe25b4fa2bb6b14632673694f76bc83faa20636f11be7f20fa349006f5d6e4c
+HASH: 911924aacd551b6b
+
+KEY: a52a368bf2f969a1a37d793ed929111c
+IN: af15d675299a6b3a5088ba60d36494a5445d8d3eecf2225290cfbef8e86b1fb333db105d24f10eeb9b132a2bb9f5aa754d593a1bf96995a3e5ad900d9b6b2e202ed87777238ec6a717ff2323c9df32845bbe0e89a1f71e22a1b603929782e3f70e09de5e18d47f07ff481f8ef04b0e6e2c4fe6f3d76f24123083b25e15fca632d83c66fb474d649815d75139dee141c96942b4b9940d10e3fb
+HASH: a3f506fe42dbdeea
+
+KEY: 6a7f1c676caba982cdd659f531b20c07
+IN: d3a37b3f01de3a0497cf16531f4887c2e62afe87b80859329c24f76ebd3cbb1f4ddd1d9960b19308dde72ea1c6bb5b5884a9acd93c5041986fd7ba8e8717c66f675141db28098523e1af51d6b31ea372c8a7eecd9a9bd7597ec7c270ff5dc04851dade76a6439aa7b5a1a4257413f6996ce2f4093cc70aa26b0418749808f11460f02ca76076e558664115fd627eeff45241e11842f406a56f8f
+HASH: 8ea572557b7fc5ee
+
+KEY: 045745caf81790221ff9f8acb4bba8f0
+IN: 260d7c94a27aa378db8d6a2a9da9d477d4880631d5b1741810f8b56db31aa1ad16d7c6f956f4460a2b1d99405c1fd51fb6749dd87cbd0df18ac4b6daeb46572e26a5bcd5e5db1b8e8b59ccc78883ca734320b0835275fd9a8bfa35f7ecca13025b603369e449e2c1f35a506f72b24b021aff6ffed29abd07478b5af22223a889017ec4db768f1ac65d6bd0618bb5b5af4721ce797626e12e0b600a
+HASH: 832ec0dcece0b555
+
+KEY: 72bf12388602a326c7cc1c9939495847
+IN: d3fb27b0d7a06d49c5fe54f085bedd948bca58314854dae941bdfeca2ff34d17d8df38faea4945795e7527bca957585e0f461e6f08ed8575693490f047edc22d04e4a3c77a9447179b2885dfc22f602da9fa0f17ed17f83ee422bb6dd72a7eba32a85017a275dec1174dee0fb88264a65761a812d74ad9375c415d843a9ac00b7c55d8afa324ac9f29cde5c3cfb73174d95671e7742917bcf1a40318
+HASH: b4f1eb30f213d07a
+
+KEY: b01c6a8a5c49380e00bc20c6c506ad00
+IN: 127d4caa813734f023cb302861fece4f776a46a1732fb0c9131c311ff977935451cb5efa232c4bdb7cf7723f4b9aed17231444131cf97c97dd0ca77f4f7ca88663d17ed60d0937fc0361383c3d7ee9fcac18811d36b7e49b2ea6e6c36bab906a11a25ef73b4a4417f716945dad3b6f18e25ae62511d5d2ec760d3deb77b7b6a9b1b5cbc855841c883c598dbb407a546b999fc31c26ce6e94f8a9a16c47
+HASH: 0931c8aab2db6545
+
+KEY: c495ae680fcbc9cd7759e3e14ff143a3
+IN: 0c1ce9df5a85811cbc326c0746fd6ff0d1f501746fe0c2c2f18312c8c8625df7b57e0333972fd0988bfb5dc827a72321391f6c4cae82007b0402dfe4d47e42f8677d2f134044a4e12375e185d02008e4fe81c6558afec5de8844e7c4eb64922b2ff968cc3f58d0a90666e4f840185ec72f0b52514cad7ac2f8087551f05a9753b9a52d8c8bac82d598e145313eafcd44570a170ccad306b0c146296b8ceb
+HASH: 2a93ef0059f5c80c
+
+KEY: 926bf63e73343bee34936da2401499fd
+IN: 8194c53c998266d9d0c158bba46f4849b399e72b786131cec217d2da503bd8d35a5625cfe92752069d5e4f738669bf1c26029398397609bd2d5a85ee6bbd3a244630bb5ee6986680296db1aa64d1d7b9d35629d239bf31bdc3a4a3984b4e0904b034c746c8e4d6d2a49cc6597aa0357df845a00a37c924deb62b77f3a15389c3188de0bda3623168607b3ea827d62b53453df4d54ea3f2a4b6f017a4959901
+HASH: 98be298ca4415fd8
+
+KEY: de5c7a655882d27c37ca279616de0b33
+IN: e93570ef6bf14f46669bbb6fbcacf70462b7a71aeb777966a5722b3dc1432362f84b952627a0f32d6fa6be2e1dab80eacdf5dfadf55db54c2e8c93a38991fd7d728f3427b6de778e12ac63e1a914f68e7841b01c322ce2f16edd00c74b828eeeda4a2cf7367e668501fac1f3308e3d2b51555c62622275c48f0aca93d8cc8756ed9bf936ca17b6821a98a8ef21934a7944bae99362d0fca7f0c2f83e0afcd554
+HASH: 562d46195b277315
+
+KEY: d666f70660c76d1aa3cbf1a0956fbf4a
+IN: db25c93ba0b4101123ba569bbd02b72271f9cd53e1ce0a278196e2577373ef4d8a6101620e80059178c246549a0ba044de308892f57f10364833fbe4217d3075b8c1cae540118fcf1ba8fefcb8ea62644461b9e59ed12aff0e097614e111226e42615ea3bcf5f80b1f1eeb5335d156beb0dab7877a646199c55a36dc53bc4b44276b5e698df21075d4b49ddd50d0deb12d953b5f90432d5afaad25833baf978947
+HASH: c34b39849d9c589d
+
+KEY: 7a5fa32e27e1f28d306844fb734fc755
+IN: 10ecc8fdbf72e80f52dc49e927a9303c890c0b31173a96ba10e3afcaebf25167a251fc824b8699d665dddc8230b960ed6431ef13778d4e5f940db1418d794e012c6b9b351d046948faed0198e2c1603d32f33cfb43f67599a850bc7579fea83cda310a73e448178c80892b66e13fa136d0c99bd2f190dee75a8e1cb4c82ee7515993519bdd4ff3050c5325fa0d4c80534f93a432e7dc3ce04c32eb60f9ec3eccc85d
+HASH: cc4f9f646046b152
+
+KEY: e0689b5340183fb889927912068fa4fc
+IN: 0824240ab7fbff4fcfffcf200d9bffd34d7c48261be53c7a31db69ba7234808fe3db7bb6eedb30519336cd08c8bb2971a1be8e60924146eb399c31166c0ffe3033df9c52ecdb4d03c73e3cffead68159f41078557716a5aec441193ffdb700512ca60e84b7e366438d55456dbbcbe7d3ee3c579a3e76e64890976f92883e050b54dbcf3d3baf064a4e8282c3ebad12fac36220c8a132994572121bc0581cba69a727ea
+HASH: 0811c1fd2047ada5
+
+KEY: 8da46e363894fd77718a5ffe68c6d857
+IN: 3b956f2042d2b790ca3119286099ef78778248eedb2d12f70a3e26381d738a8369a4828b8e361e5b4deef28e7acebcd4bfa231548b0d94cb910ebf819c605ef37d93de7465672daffb2cd923a2e812b20a34569aee20ed20cea5f5f5c32b9cc9f09fafd58c18a05e9822ebfe21d461bc29b2017a199ff0dddf06880e385fac6505f91117db236edcb086b381f61c33d5af6d2ffb9cc5382a7cc77a7579a6d8456fbcf7eb
+HASH: 23263a4fca4dc1cb
+
+KEY: 3a0ab8c2c4de1229d5bfd41d75978052
+IN: 7d3ce1f1eb5fe650d815124a0f3a2d07220ac9bd487367c6e836b669709c9908ac0de2caa7b2e793ae32eece0e413f2f7dfec4ca7edfe7af975bc76fa84a4c9ff21da673de4d1f3c0c0b594ec0b88ad80d7f85e6aaff2b716e236a551e26cb20f3272ef4a60d46827ced1e5c1a4fefcd315c34d7611d985f2f6127ab0c69499e17de7a99fd9788c025a9991c6a1d79377ecfca18f72853192dd407a2344a51893cfcbc42b7
+HASH: 8221bf0da6c9d0d3
+
+KEY: 38efa39401d4d49225b67bd14f103ebd
+IN: 4c16db14037807b7ab4d46c057234d05be8970ce094b5be27a2346394e77e777b66e623633dfce3dfd941f8effb955e41fcbe15c8c934a684f2fdedc270516950dcfb16a99208335fdc69d913e189b04e4c60f71cc714f79a9523840f9498bf419862108c2285688db934605921d2c3b04a8db17cee417b8ea8d4cfc2bd67380a415bfdc2cac0c071845d7e6e4b967275fb4e9ea3156ab23db809cc986a0cdce5b09c12d827b
+HASH: 4f1e74abfdb697da
+
+KEY: fcfea01b83e67edf2ac9deabf7b30cdd
+IN: 105e560add4f7bf41757314d320a0741cf5659e45426fd73af9327b7408ec22fefa5aea59f43fbe0ec4a2fd0029d9779a19beaa10198084aa6c1c392a3d25518e9f77f83c7c780a129d87f06358dc90601f96fd25aacefe3f08612a98455e5ef9def288aa840a53d062d9e32d6729404fbcc48acf54784a715c13181804ee8ee55b68e0d5a43daf90fbbc36b22e786d895d6ca5b0a1241bef424052fa29a060b861c344e68c234
+HASH: 457fb4e835a8fd94
+
+KEY: d8d385fb07052df7511d0ac37d760a64
+IN: 88458bbc3d96c209e981e3b3e7845b529f373b80fecf8d4f3a5e6db3ee2fba920067733939598454548183bb425523bbf4cf9abb3942ab09a754f760b381b7105d2a31aa76210715f3e6bc6c79f62e754dfefcb0db512ca02397c2f00a169706a5e602ed64a62e307ef506e249b26afa2bf49fb4af6cd8b821a03469b0ddc4e054c6d5368bd2d69545dcb7b1df7fabda3edaa8c8c2ba9cfa9d126bca2defffc67b23c910003e05bb
+HASH: 2b23830e90785de3
+
+KEY: 278c8962711f0aded8a253e4b8ee6c0a
+IN: d10cbfbbd89f11c12183799340af2ea59c6f83465e864736dbabfe83d53fad727d0b432172cf68fea351f69261d63cde5753f8ce15afcb728e60f2d6f85e02e8089a5f6e230595f0664dc1472bcce53abdd0565744b6c58f3c874365a76efeb043804df302dcba2d7550834a443279889673f1f1b305a27da243ddacbb3e6fb60451121a7ff17301d66ee4081b09bcd50d94eaa1a087905085fb84ee6df10224f953f2356e729e53bf
+HASH: 2e6f8d56bc4ffa70
+
+KEY: 12a1dc868d6221dc01ce0b91a9e3292b
+IN: 49ef1e9f72c7099589233022f6f88ddfd33eb9b078987d92564b014312613be083e108eddfac4a202904056e6eccce225cf259c35bd2e07b23e68240b37503c5c93e1bc497685cc82986fed06c8ed474cd4fcc59630ac1c5dab3bd5096264468910f741622ee6a42c75b5bf899e64a0bead1e68d44f55b633bff935bea0ed7f9c39d46c1296ea9c690f341b30a482bc04c87520a64b6cf2c55110e9d018288bb162612ef0a55f8bce78c
+HASH: c15f435c7372dfda
+
+KEY: 04cf89ae0df7e320e9adaa73df59a141
+IN: ae5d6ce0da399f5b9fd2ba3ef2ebfb8f039e41cf723eac3e52fc599b07e952fcc9dbbba12ffd22c6ac6c7135a80ac0812f6b09457806cc5c02fa6250cfd65dbf7987ef56c180ec5c2f6312f98e9a72369156329abd461845e9b02a05575f07a08fa3f462dd0cb618f752a4f0977eca76da4f46cb22987c2e6854c9828ad7cd03863b0e3020ec6737fbb48c354a0bb8214df8d1172f42744d5d3878a27acb63dc6a004a1eec25074149424e
+HASH: 511dc81ec62424dc
+
+KEY: 16ecb48798f97a2c4a8ebe5fe9b1b375
+IN: a4a59d310cdf2c1b4acacfb9e63d2e1f1244c4cb48c665d72c79cd70453680f98f2753b1c5a5a3323b1d33566ef0ebdb80d0df3e5120c6152bbf4abd3572a80d98e583a55cd78634b555886234776e32847f8d39e39819d2dbc9b7c4e301cebd02b12559192dad2e06a5cd0a79823e46ef80f8e645e1b4f9b3803cdb6ba8f8e09c4aa9672931bff14c558a78f1309a006d2f64cba68537e4aee3002d5a9ce6a8a23503174d98e508773826c4
+HASH: 080bbaaddc2f3aca
+
+KEY: a9b3eb1c9fed71274a5cc15e1175e68e
+IN: f1bec245f19516cfad98d7262d1a3b478fc82d78df05739cf65f5e6d9ba7c986f3efc3b04c947ed2f0fbac09d2518dbce61526e9e1701e04379245d178819b1da0376d2d08f38c666434a07acb7918696eb8bd81606c8114d348add06f385c1665a783aa0b33d443e1d7277fda197165da4a905ad9e1b5468ca03fd818f0de7414a5a36e96ab244f61eae53ae9c11755d9bf751cf2e78c902377270354b23dd6241fc56128d058374b2090707c
+HASH: 4ccdc25aec4ba25b
+
+KEY: cadece55ade80e5af3978ba47a67dcc5
+IN: 1fe9e5710d3c0c773d6b8859177e867bf08170e6682939f9817a55ad53519627a59d5a9acb9fe2111db912cb0697f6452b33d478702f6bf03bc92465d7f1ef1c3a7517f791c9b80afa76cdeb5b35dc7c5a281ee75a10e9b27a350bfa7a48fef3033fa1bbe3f2601a3b9da372df9233a0ea05415d2a5289345a1770aa71f6afeac2b848d164f63e4ab6437669615e46d27102d8528d70fc76d61c2e9cdd2468311e4cf32eff087aa79252c43deffd
+HASH: f1e25541eebd5ab8
+
+KEY: 84a27a58c3b295d938ab319eaea838e9
+IN: 2f160c9df2cfb78e48e40097671d58f5d7091b789754da01fdd86e3dfeae138151a618197e7c171f29173e529a0750dbbae1331ed480d116190453b1ea8fd1fe100a6333865adba4b2bc1a54f1fc74e1d0453e7de1254802db15bc0c33faf00aeefb328f3b28bbe80b21b71c9982a9074563e3a4c23082128f3852301833cb68946b51ab5d27b8a7c92e4e4d79938e1a92253efc411e0744a4fd1d4e8cb8d171a154753a1a10bedcd310f0115bfa42
+HASH: 9c32983911d68018
+
+KEY: 66babb962005f1759419a54c3858f983
+IN: e280ef4695c8ba99cb645c0625dbe09222f3c010aa29569b54deff561ef13e59c3e9a8f5f76e93146c1ba6b99ac541520dea5adb84a23395c281a6b31c18fa9019245f024054ab9f6c0496228186648cf72ba6b01249028092b2ae655c80abce501c96b2274d70648ce2eae53ee08d60f5cf719ff96ad3379654c8575978f14b381c2c8da88a2fad32372bbc469909cd390bb3dff628822aaee61d17ff04f16e819e10b2fcf5bee6029d6b1da03f6d66
+HASH: cc54beb20f2d80b2
+
+KEY: dc36bfedadf2e697f77984e0e9b17588
+IN: 97743e1750aed90f037cb783a884de3826b789ca544cfcd8c5b76e35f895d75f37f9c15c4b99ae656cc8abe6019379f76002359726e3b144e24bff09665e6c24cec3811247a2ba6dad10d0fa2f7ae0a63863ef20e773ede65f5483460945696d4594af5090499e44503e74cba836970f2bba2407b6da628da8153334a320f272eb995095a4bc71db29f5c597fcc7341c37784333370f88d16c37e8c6e4c06e641fe103b1d103c633600d784a9dde0a6283
+HASH: fd468e0f40f32894
+
+KEY: d24f95a5b91e2350f3cefd0554bddd74
+IN: cd119df440a59ef6b00fc48821a499c61b8075a3b22fcc688c7e1f692e7e3f4bf32d388d332260a1d8d19fd08b75ace47f8d723cfaa4ba09338be841a45d9a9e6710a72c3df42d651062062c99561f64edb9b7eeddd4f16fc27548333d08ef7e53e70278ce10f96c007786a3daec7a5102acdcbb9e5e60f48ce5a3707e4b302315a191852a70d60af7ecf808a48ebb736b0d50579f846fb2cc836ee0326d37c9e1a23ab4c084e682349c13fcd9c86a2632ba
+HASH: 8285187584947455
+
+KEY: 1704ef13ef28111c9fbf1d8a90ed3df0
+IN: 0a7847cffeb15d085af98301ecdff96a65ed9bd8b4f9bab78843352b449fbcd770a4058558d4035bdc0e2792a373c663824c435c8e8b18654560c3777a4b90b39309c7aea843ed450eca8c31a5dc0186745ab445a033254c3d7e5e6aa15a185626a5c3815e5571cb1af2d9aef4d3d52aa6aa845ce8d0880d1cd4bc90cb921bb52d1718c092daf861fa19cbf9c0fa830b0d7224764d01259fb23886926b50a7ae1c5800440b2dda17faa360011611716d91f926
+HASH: a9a18028cc49d5b3
+
+KEY: 79ca5921362a58d045b88e0a295ff5f3
+IN: 238512ada01cc415f77d12dd755b1977356ce47adda5fd1a21585ff3e58da64dfc0bbe908bf12cf65dde9997d75df49f73058c13d5aa8fb43608517e2ceb9ffc723260a7fae31e642c987cc131db9d423f762296608d1acdd10a47fe8042f26952c26b92e7008e3d356c8888ad8de121396145cea0b5bc4ad38eaf2a9d27202d639319ba092b236d4f837d8b2220f97f2d274578748583ff6d9441b52fed194c781f05dec21d226459ba2cca9e5822eacd9a0dbd
+HASH: ec16a5874532d913
+
+KEY: 4638aa9c30b7f2c3cb2ec0a91cf25051
+IN: 38d6e168f88add060593c951113f8de24a52f6308fd547653f50f51e366547aa97c1b3520bf3d109fd5ab90c27154fd94ad2516bba6b248b0ee289781bf76b88c00b28898affc3ac9fa59dd386b9dd97b9504ccf7a1b2f233c4cfe091eaad78930711bd3c7be41bcbae01eda41f1eeb34b1b60d00536890fa3b1b26defee5ce964d9b6a30fc7645e5f0bdd4fa6b37ad00ac09d5b1fb494f62245c1f3a7b829132dcbd4290d98d77cc3493dc15a15703994c86ec6da
+HASH: 99f7786350012739
+
+KEY: b534ec6c31e22214349aebd7bbe1752b
+IN: 0f4ce510497c8751967724da17867ec2fa94213d7a8856fd47f043dbc98d88b29e6250981e9aee4622b18a7ec345b30db72ebda47cb0d51b602ad130e880cb4fe9added6c65bb813342b434bd5a14ce77c942c311ddf2035fe57f02ec143ea99ad88c72120697add3c4ec612084f57c004fce2cd3c21e2be8e0445d5aa808a2448f580b8a72dd05d7ecea3d0fb7a632c7a74acb86d759f16523d4e539ff6122d10b830011b0b64c5a8cafb88f8bebe73f5c7218fb753
+HASH: 2b5795dc26423d82
+
+KEY: 9f113029b93fc164cdfdd12ea7e0ffda
+IN: 50e2a2d204f8647e339608ea3577a1217846c9a4d8275c73afbbb867c1e8dfbdeb6e2e429b6fe6b99fda5e790dbce8b943bad7f94cf3d725073f55c64093ac597ffe4b05cee782096794ca3679dd261e935d83b626f56104f5bd642da423ee3228e9a15fc59034faffc516161c9dcbb55c4e881d6ff51c6b9aec9f2c836c730c152c42346001648fbec676564b204ef7e6c61dd8e20e8d7da0ca21625964f98396839f1065b0a3c11f8234fdf133147a13b26fd3379eb9
+HASH: 9ec5a9511a23ab04
+
+KEY: 77d7b10b166f5a87af290aafa2f89685
+IN: b77006843d57fc1202e0e9e8e058fb4cb77ba199d2e08adc3165463d36a2976f2e0a8a20ae95d28d0b97febcadf861af46cc88efee3d8011b7d4c63e2679bf4a72539f9dbfe9ce2db404345d6d2bd767e1c5a59919efef9037a5b6285e766ad9ac92e15e3e68f831c98ed160646f39c040539141ea91e90e7e8853652088bbbf849a7edd9bddbf5889275d02fc7cf7cdd35b6607fd4597dbfe6b615a4c8097c345d086730ff6f27435527f7b9c5b78928e390d5828e956e2
+HASH: cb7dba8ab55e0d82
+
+KEY: 1171800ebea1b1ff4d261351b05b3264
+IN: c0d61e58cce4facfb83cc1cb920f20f2e294ff8acdc5138bb0f61e9143dd753f0b6d1f7e58f2f25585fd353220061ab0ec0a01b9d2f85ac62f7fa9bdf0ad132c96543b3dd99aa06bc379c5f2563d158dd4822b4d4ec6de886b45337d29d5db1f46838d7f69c43ed425afbcec67bb6d61f76248d83b8979556403bd213b7b5af1be5d98f7c221e91a96e07bffbdbebe905dd2b23c7728e6a8229ced70da22d7f2175458c57d00d9eee6d6faacc6a8d376bcad29839b1b598b51
+HASH: 6a8a679785440307
+
+KEY: 4b6cccc67b66f8f146e2f94820531037
+IN: 4e3676a68fb5a4b0ce3ed5c82710a6e8f536cd810c619567352a65aaa7feb7981adb9072e8851908cbbe4f9cec829bba2b1ff542b6172fda4c8204be0e9f7933708739879f48a15806d9c1e03cde5892d453631e9d36f9d1be055f205f242f261b7499cc4fdf2fc2cf77a477631112643640d4bc394cae8e530741a296f1dc814a5e3882731117d213499e27bf4bac4e358b9740f14f93b3aa7e18e8bcddc78c13f0a4ed6135cb2fd70fc8affa434df3d026417f7fd8d0e3a44b
+HASH: 27984128c2833ea8
+
+KEY: af3b7b93b04ccb242d6e5f712ea36eee
+IN: f432ba2d3f6223aaf5c91832408d2e4963ea34a7950dece6b06255ded67e3a9857c36cd8d1cb745ee6d24ea498e1a6ed93b54120719c1ce5b801b77da662774ecc450946f12d5e6408f6584a7c665f85caf59a081c7994c7c34f63b1050443a59232c9f4a498d0939b181cfe51251f80927060d1442db2b8c07dcdf4d688094d5b12eee1cb54216acc8b5f75a5901496d23bd9d1115025982ee04d8285a404637d3438001043bcf8f87fab445bda616dcdbaaa642a18c0bf7c6135
+HASH: 80b732324609fca0
+
+KEY: 50141e86f8f9671e95fac62f082e3924
+IN: e14ae4e8bc9e02f5ae1124ec5c7db0f5f626e7cc67e1f5b8eed1a51468d332dd0a5704b184166e7fbbf40608dd3733845f5d0445b413ae56aaf51b0049b363f54e2b44f075da74c86e90fdf8b8dd42914fd46e56113850a5b038165a2e0dfc531791ce21ef15d800503163c4d314dfa5ba7da19d80f7b63cf903f40d030ffd7833d98d39a206d44d2d3f901df4cb7872285644e83b84b300a18a8af2e7714b8562a984b8f091c161cf63ff2a27bcd1c0fa74ab2c9b4659c6d865e83d
+HASH: 11cefb921f4e2a54
+
+KEY: 3ab1d3d853fea4c8c40da2d64ab8c34e
+IN: 3e14ebadcad09ff9550fa9a14a0ff24bef1b9acfd979ae767ce7c7f316ed56a0d77ef6d205ba315b8a8a8e931f365753c673842f75d4449d8ddaf010fa6b1e2cd7b7b7c49cc85231a827c1549836dcae35e8ea7ed914760858d52e9ec31ef4435485ba98a98e065ae13648547813bcff9052fc5ec8048bc17b06dd05047235ec8ef9eaf2570efec25933877f44f06f3d2f4fd8adf9103babb7cf528abad21431ff18bdf0aff912e2b4e6850f819f349da617626a204b6ccb28cdbc254b
+HASH: 851108e3cffeaa47
+
+KEY: c39b333d7d4a61a37122e1a399020b0a
+IN: 428352711ca82f0e4f10a53adbb7cba3eea77065db8f7c4375c7533330682761b095faf2744b17f057a60855b6a432ff18f46be9d9a6ff928b4a1dac4103cf9ff6d8a24f84c3709be5a6014155fdb66148d4c1286adf18ab325f4486210b941e48eb29cceae0301b786b72cbe7abcb4da0caa151d010a64dbd6d1b36a1dab555552dffb0acae07e4b9ec5a3a3e0909db4049293feae4351ace9ae8f49370910e20c84f6ba6b6e440439cebc0736516cb8efa4ec594eb06fe335e72c08f8b
+HASH: 4ad30b7af37ee5d1
+
+KEY: df1f17488985e7fe097d7ee58e087a73
+IN: 2ceb8b8694a92ab60a31c6b8f1ca7622ec9ec5b3c7548540feb0fa63846ef5dae7a1d0566cae8bb6936c658dd004a2ae17c50e392c37c8daef2d10494ddc7f576224e173a42b549eef4a53afe9c0de7c36d3b3f4bd34daf6d143a3abe08729aa6f9c7e373865ef8eff4a0d11c4d632e14801c6581c7bc87f0db3d87e82b9dd7bcc99b1ff883771770b12c755cdc5fd2b9697c1a49e17bd3beac0f1d094ec995fca8766fb41f0780cc0c6837b620bbf1c2be83a40c599072a0fcf660d39e603
+HASH: 7d1899d71fc9ed14
+
+KEY: ca74e3d171ff6157d16dd34deee34055
+IN: 2799d78643a39f5580dcd35258bcb28cb70e11ed8196a463bc6077ee88545b68ec43ccd0fc6b6e8e4d0788e8836554c6f071199ddaac02e81496017703800e7f30c79d420e2ecdc02ee1af6491be002d19793f50bc1070cb2c4a26c63b47c9fad2c3b5e9bf0918ac7f020dd3d88994b3750644eb15189d5c610c1068cb57174d645bdb1a8cb848940d0cfe9aa142c148890e4781753ab5ce30b5bf8037f9c78a4bd8023beefdbb00df2899a561fef1d80d33d8ebbbb87006becf7f0120f37495
+HASH: 1c2be2a14e15d1f6
+
+KEY: 31135badd1dc6ca387d00ce70579ec42
+IN: ba28ea9b2002a941a88611510582291a68b09b61301cb4abd0b70647c7118431aa435710679f462556daf91b587cff691344b8a8e5ff9314ec33b9bf240cc03995d968a52d35ce359a34d2f5a49c4ba61658aa65c3cde58ab36f46131f0513e21a9e199e00a8626741a641b53fc910e6667bb99ec2656866a90558ce2056bef7524b8c90c531f17003717257230ec9d3e56a60161db370b3958b2bd91723f1abde20cd69ff24819ddc50df63995cfbe1fb7560d24916c2e328568895b1ee7ba0b2
+HASH: 3c0f1863d7a2d4e9
+
+KEY: 039bf03ade84eca468720c36b402d4cc
+IN: 2bd881325c05856a1734e711ea05d6e541616bdf20bdd15efb684d8e59b57ab8c0821e877283210ee3eb0cf6730ca441850a6320a40eff262df7bcc00978ff53a6027ba13788c0ee38c31343d0f9aa2cda03766c6e77541fb1716345eb5c65d13f0140feea53a98e30a2c3c8c7d1c47d99423b4f4bd688f7d97cd9595dcb656abfb1ae6bd5069143d096274c4cb06be3994b7533f5dc3b942349034bab8364c252b16e2b31bb4815b6a0f0a9487865d093c22ff0b68e23d957edd4a5364f4c8ffc98
+HASH: 2fb62b186e731663
+
+KEY: f856d5b9728dcea44e15d80ddb3b6b33
+IN: 4874462c3e4302b6ad627c8ade84d1117f33599c56831b7a133811417e6993742637d8453551931456413c6382f7563b77c93bbd9b0d19704cbeb5ea5f1dd6ee7ce41b3f4511f8c9e4879072168ea0eb9a010032d1c219af6b9539124d2df8f3ab76d12a436326c210ef6929c138ec4e019e4362c0e3ad2008ad90a22f8f38afcbf2c056c22cebf975ebb1fc1de28769466cdb08ad033a98a9988dc8fe887b7ab879614fa3fa2c32967983b268299252aa380dc18c14e88991fdd69040e868bcc05cd4
+HASH: a8a18ad22ad98ef9
+
+KEY: 75c2435bcb43d763cdd27e211afc3f0b
+IN: 53383c8bad7c0d2b9f50b9c241c90b4d35db5a93e91f3b00374b83c34c88f3914cb2ae9af3d7f828022e6c096d22527614830010e495f609b860f68f7e42bb95fa76862a7309b4bff31037500c73220ff9649bb4e2b0a80cb57cd88d85f88fbe79f34350e8a28c4f9d99badf1ac1d869f8c1063497c7ea7e51a5fe96341bf7d448d865ef3a199fc68308c22d4c3b41ef57bfa185a50c35c1f3c79bfb77395eb9cdef055b26a0bad6bbd0c038300f127e74dbdd92c986818320f4812d9c85ac2b06ce9508
+HASH: c85bdf13c1da265f
+
+KEY: aaa6cf947414e4c120c6efdba9f474dc
+IN: 5f2eea1b42f0984a7088d6fa9b9c87702b5469a55372fed5689d3063ae338a8ec902301fde5e25f3ec0b533427b8443a66f0daa3688104cf1b5691e1336bc1fe487f62dc2e78ac879f5e488be2f6e1dfc2d9144f6bb688ee76f545dde08b91a6470ef4c22a391d7a665978d38cb46235bab1d8b8b0e7944280398cdc0cc643a58fc1c6c646ab1766c1cc616ff4ec2b6f2ed0662a9382434295f122c877736c3ba96632c4763c89ded4541a95a8066c5b6e30ed9c025b07887b5996439c3e13e357a49ca0a0
+HASH: 0fc0dd702f54bf34
+
+KEY: 299646082ee293d1126e514a30d75da8
+IN: 91422941ba7d149928ddb44df20cf55d71e2056ed59a569be53bdfe40349f066cffa24c1d55157af584af63558e86413e6dc3617d090a8328badef05590a6d2336ff73007342d339295981cb5c4b6cedb0808f5e48da37b6af2baf00f9e491fe2e1ec078a7046a15621b921d1a18e1671db82e56ad141725ea37bb7e736c8ce98ca1df77d1635deef933ac635e1bf178ef4102b6bc2b8d8f93d42f1df1bd7d434f7b451b33a694749bcb5e23d62a254cbed73a2273a3c161a7734cc8466a025a42ea712237b6
+HASH: ea1b7e5156f09163
+
+KEY: ee08dbf30fc222795a5ad75bb6c3714e
+IN: 082ba0d74dd5dfb47a49ca851322acab428a9fd84148e66fd5786639d791444356bc79ad9d8b9ee1a2a02d3fca92256ea04b7666708723e2075ffaf3dab7ff1ac397393153e420e8b623634e37e051fbaf5c0d48eba065345cb1d7d62304a88671043a1f16368c311ca501143f109e850a0f2943a17494be97d839bedd35285c4b7fc38b59280595db53acde23d78725a376b17cbcb06325e4b6ab5b0f8d0da4f190c5a288f4ccbb02b59b1d20b40fd8226051cd29ce948bbba97b526819dd93b85943ae6d42bb
+HASH: d45c3f2164c5034e
+
+KEY: e5fa0a4bb5313d1b481c5a018fe78952
+IN: 89effb15fa40020a554fea99be68572c03f02137fd9f2a41b8fadbdf6dea0e58b0378ce72c7e9ea4c2222b748aaeea397dffed1337a80e23beaeff1dbd9a16603ebfd53499116987660c8973752d1c9826370185be332bb1ac482baca01cc566db732da2a806cd6b7d2343879d137704414ff4dda4d82abd82cc9c5a6e5c4b6bfffa0995ffe8b0331294490fd05c675fce48bcf1480d4fd0f27bbe5be94b8b9dc1405a6ad757af9f4eb4b1d9a2e702bede28e7c7ed4781d244c2604c1e8ce8ad5316726a3c351bc6
+HASH: cab01455abaff8c4
+
+KEY: 1cc039133a9fccf3965fe20292a9a6b0
+IN: 058962e5b3328cc3905371467abe8123facddb62a59ab5f321f6b3cc8c43f58168ad8596a5b8ae47a18f327a89b0916e7b5df23dea5f7917773f4bc2560ae7b65ec57a4de0f03708e8ae7e209e5136f196e6a731f39650ca5fcd35355c5e77a8c3c5b31ad7ad1364e6ec11a86d26a9aabf0a4d1d1cf2f3c6ddb399fc4959f2662c0dc56657c55e451ee0e7baf581f2ea73ff843d42e75975e06937ab4239c0d6ee67b4ff101cf6350e0f8c73ab63a590769ed282d11b976a92eccc462a58050514ec2c42f11f546861
+HASH: 57b4c25b6cd917ed
+
+KEY: cb47076e391ccc17998bbe98bb92e790
+IN: 02a2f35de13c06d3f20322cc427e8dffef4e99656acd8c9a6ab28f3eb9f65d41eb54b2b5c7ff8f77ba42a88c1dbe02195684c3149c1875dfb3611910245c734479936a94aae8e337956687a062d82654ac1f688171da51331bef1a180975e4f7b4711d587033e6b7705fb44fcd4d746e071c34a2c88fae2af09f1f7335def555f809ba8c85abe1af21ac4e67bc30d9dd81cc8a453063286fc090c6a08c2269fcc48531853337dc9707811eca54ed466f8d7996c46b599b6a1e82b1a39d3185f98c7333c87fd4af5121b9
+HASH: 3b2294558b1e0353
+
+KEY: 3fa82dc1ed52e4711cbec3305346dfdd
+IN: d79baa661ae8ad2832adff360b38a1779b580f6357eda6b37911c4197854b5aa7e63149356e451c57480e1e149e2627a627e60c3e49b60eb1a1e5c690dae76c0d6a7112e3d19f0e8041254d07ffe4c21d8d931e889b0ff6710c5a7462027cc7e67266a332d596dfe6b854fc00888956e58f7babbbd3f8fa25c2024376f0132c78faf6c9559b18f32cc14e97b925080070e3ebfdab0f81a7b05d9d597329770f778657facbf1732f12fdb080e220960b7835717ef9fa58f1a7b535db36db9f2b944db2a6b63e4492c73b335
+HASH: e867bebdc0e185f7
+
+KEY: 4997e7ff91d638de29f22687c97f9835
+IN: 568283eab6f40f158e5c6742da6338d402f4d05cea50d6e18da7e3b0556edf1ca5121a58f44d9c86066b917b57cbe7f17f497772be54022bc493de67201e60268a23d3f0e8024db58eb3b6c4805c326374218374c5927d60069c61e37777bf76c59f166079cfecd65c4479e98859c514780c07d7259d2b25575f56ecc936976534537908de6e4d2965ef16ca8af35ee21b937ee17a8a125fab11a0f59c450f4b8c3b5d1c5035ea73c5d71ce2ffaa1689870170b4414bfb17faed8baab239a504027a7770782c8e6c25e02162
+HASH: 6f9ce421d15fac38
+
+KEY: 02c1994ab75a1dd3b6c46791272f8265
+IN: d5a7e586866fd0bd586e76e15fcee45323cb40ab0a7c92030c07d9ceaf315c8e8e10c20620ef20b365f693c5b71011e05afcca7e74f2dd92385a6fa9bafd6ae6e97a3a4195aa50e75a223573f5cfab0ece3183f5ddd9580607323bb16fc819b3cf76e0d4147830cfb7c9711577707bed8a224c806ce062748a20c8f386609da289827be50904e960809e0ce76ff4416a3d9cf82b4b5e3c96ffb11ffff6d846481f2035029d3c1add9be3232121d3794ecb1bea6058a299f099118c20be4ad5baa4e67784f92feaaadd54262383
+HASH: be78e88292bdb465
+
+KEY: ac06026fb727ae3a1211d369197706d7
+IN: ee203a0e9ebf6898bdd645057f8afb27ded7d7e9c1660f3fd27f4489cdf3286a79b9112f1ded1a878db6336144b37a591ea074d6ac358da8f08b980f4739facce60e0685ccc3d939255044996e060b082e9055c66a81724a3e2e00aa0d4779bdd5417f23a8cb1e8364c0404225700df4a06342db47cbe185629a8b4e14f14c24a8c6784f9168eaf4f0ef312ebb18d6472d63e748d0bc2b64f8d31035bab3aeb9736a22e2b3b42deb5d97a3c405bea74049086ab4621658adf42293820bc735bd4a748677f711588c5cb7986c6c20
+HASH: de3a48d266533d5e
+
+KEY: 93db71499f0fe1bb7035d7a4eac9cfe7
+IN: 182709207410de3757d35250ce4483017e6f1e8bed619dd367b9e2761efdc1b538006730fc67f57919bb47f9022ecbd88db269aea43693ac22206c4223179671155b7726e5c87d7ba5abdf38b1d1380bac5a9a52be718a37899d2d35ca1da2276a42d411a7c54a2ffb1ad98f7c247b3fc48b30c54a342b03ed474d7ccf18e3c72c8ed3b00cd5680ea8a0b8bef4df50f12bb6df3352b8c7b10d0e6a4a2a9317c8f25b9a1340356041208d7bd503bb6a66de10cdf8775b56b198260bf964720811933dce95d39205ff8675c89a321545
+HASH: dbbfc5d600229e64
+
+KEY: e8d3571f69d87217642056d3c53b5f1a
+IN: 3a00d589366a4d245980592068b36e1c4b9125cb93c8db5f4269167eb99dab924ba3b95cd0a7688bae6d3a52e12c463e904a4525467fc46c31e32a1681c0b0a08c1738caf7beb944c676406fe3eef522a873230117b940642c651a947e0edefda4a9230762a12cfbb4895293c4290b8be3d04ca14307ae885f348a25b8f61addb5b8cbc942f92abb872769d4c9e8e8538224271bed35d767bb77e1723fb09208412e604e1fa23b6cc43c137551eb9a963e0b8c7b622f454a436023523ab27aecf545768ab50fb34c6ea4b88ad704db9b
+HASH: e5d08edf17eab577
+
+KEY: 749fceed424ea2c74ec4418aa5ad47c1
+IN: e8b594f46e60f77f4596e2c8a90ea2f042791fe19c3ddb9a69740f36cf25469605a0f16065f0cf223df2eaaccce329c25c91108df87ed1f5134c53037d980b3eaebf94801389802c32e0866e2f499c832358c3992786bde05779f8eb8c9ff1c81ff31e626f9c91e615410d5a8625f4f37536059df6363d7087d2f47ba9c599528a7115ce419fc291b006e3dcad7c61840f5900cb4687d8f9206700b251c5a6420fa693afe05070537fa87925d10d5fee47d246d5ede432f43299e823ff838b8a6485cddfb5bfff149823c32688a039ee87
+HASH: 6bb82b5bc56b8451
+
+KEY: 1a5ea3537ee7a7a2ae0a22001daca94e
+IN: 76b8bc81bc9783f6f8a7e85151baeb8a62f8ea9b6a7a6aaa51bad995af4e0369f8f61b6e6475da34fb00fc1940f32f2dc0b1ab0d0a2a6f04ae9480f368e455e6437d2fbca09a5458095bb89f6cf36a44b5ad3ae88b34191ac42469a3c28e9d8d29ea60397d01ddf11ba80dca571b7095bdd08737df05c982b3a115bd3a87a7edd65d988ddd09217f3f31386676d6ca6592ae707e51f16849444bbdf3c2686763e4a7ad2f44fa5959d4ef1579f5ad937dba6c39e4cac0f278466350bde9ce81a9296c2e422f79add65343d8a67d9732d72e10
+HASH: 51a33c222edcb979
+
+KEY: abefde6166ad815d7364b65e911585c8
+IN: 0a7c0e36d9fab03b4a53a627d25262379c17ce645d6dee8df4f333d512e37bfbcdd6ed77209fff0897242fe43f57677b246c4b19786f9ea9e00191ec25268b4a1ee715183358570d6b45a93dbe2defe8f20bf0142c2a1090652c14e2bef6bcd5067dab3dfa701199f6d5a14b363492b0a0ffb6516c7eb17953701cb69b78b290be408f23b9ae79ea9b72d6795d33b85119ca5c6fae9f1a28311e80a3cc652f941b0a94172bab5b2e5f95e23576234ba3dadb320f69752f8bd6187934c10238817c11724a7040f6dd02c2b69bd48eb1dcfffc6b
+HASH: 09e933b789e7ecc3
+
+KEY: 2563d2eb21e7a757ce3133b6e85086ea
+IN: 568c4781aba9925979d203afcb6c995087821486609d3fe07296f541c497ac43cb00e44610ad841f9e344d1ecfb86c2b27059120a68e1249e00f8cdb0d04612a8d4ee9a0c59a03fdb595e567108d6daa3afcae8c2190e6c2c29ea917361b9d68e496016aa2266523fc582f080f16fbc8c876ba574fe1c766291e378e3d0b79705d32a54ca421fb45b9e27fba4e2502f82c422dc0862c00cff5f309eb45c635f1ab89345ed4b817a0cbc32de3e19f3dd5d69c209873fd001f091c04080669a8baf5f6944e00c6a0455dd8eea0b73c388fff64b43c
+HASH: 19ac849b2fdf3f69
+
+KEY: 4129341a51fddf4cac0364142b916150
+IN: bf02d01892c4a2f1d3f7d09945bb8b8dd2b0a363feb64565ad4ac4000bc54d3d564e73e429e9c5ae94147b85fbf532d253af6fa867c155467591d39a35e0b68de5775f53f875ebb9e363edba916c1092909c15c745f34acd6d6ac09e0d8820703aa9ac7cdf141bf40a87c9d1563c5d8d17ac6cf7cc3de6fbb4cca5948989cf292a582a836b6d293677b51e015cade0491134924331bf94e3926c81e84817eff7877d5432be95fcdfb7eeb17f40ea151ea360cfcdbd20e69b43827936ebf5ce8f9065091584d79dcb5562a9da7bc3378731e3619be4
+HASH: 388b701724a52e01
+
+KEY: dbb81916db9909090e64fc0e23cae7e6
+IN: fcf78a3867f7a9c6d585359e18e67bb535c5925c8b2c6cdf9d08d1043d0785a55c36dbdd6c8b8d33362e84e8156a1ad4dfc63f18f003a0719a51e83928e79b703b0324c26897c6cb65b43c25d396288d23576032c7570f54121d55bc58bf051ae1c447d94860b796c259e24ccb3f2fddb8069537415a173486e09144837be1ac318f043de66ac72cb663cc5be42f6bf0e243170441acb7beff6c3070b54cac5712d4d95b330ce153eb4978112daa11629e214aaf9ce4d65cac7e5af9508aeead765e0b305eec25d7cacce52956cfb3258f246a1019c7
+HASH: 54e52ffe0cc75930
+
+KEY: 1c4d246d801bea4ede8caa08359a3052
+IN: 06c1b4fd79efc017ca2e7951d2c5b0351e66203b38b331a1470f820e899f5bb740425c8beb3aaef9e5a3aedaff0561b9cd18f723730efa44b55a4f23aa3c9deed14107517e2849b014978716d9ba9fe2b04782139ddaf7e711695443c2bbb18c4242af5f92cf8f2ddae4007f6c7f7c072c5ae1bfc010e3eb843392d139660cdf31b3367e24fdd5da452211fba2398a52c3dbb63c8cf7367eb7f5fa5401129f5ad707d7d0bdc8d6a551152050885ea17f58f199e7db9fcaa1784ba0e7124931cca9bd60fa45df20beaac79529a7bc16fbb0da3886ab67ee
+HASH: 13b535675c3671c7
+
+KEY: 89593db764c35959b384a1070ee77a82
+IN: 036dd2e35625b4537eb938d749a732471a6846496f98c8d92649894ea39df2d6d480ac36a3480b3b9725a5db9610a48696e843fd35f2816b7544d38eb7113f0d67405867b7e96a5159e7c2815c3e5f2ef9e964ec288a71d0a85676877db10e137e6eaa9261be28972f45c41bd9aba896fd8aed4fddc416bd90335bbe5254df22a6d84f1f7043a9a57f603c472007e58fa5287266ec2ea4173064bd0355f2cbf705a37000e71e790670b6a6f21e8676ffc6449980b94763fa6c7f267caa6c674f9ea7c2a7f72b9abc74ac83d199d77ef9d9d129348f0f6408
+HASH: 9edbfd9a1509e0af
+
+KEY: c26b64248cd814a5cbe5582ce0877f12
+IN: b2d4d37c49a0a9ba827d65f47463e436415e6e0303a703e9596c503ecc04e071fad32f8ab39b1a86354cc8b0327333150ca21a8b7981f58fa94336fb314cf76541f0a9a1fb88b5126c088c1c21193ed7b601900dca8138861968bffcd5925795ca31dce36b850e45a86085cf9218f2130355a86da21d662f37936f03805d8f7a68a6ba8e1c13160919dcf66d176725ea3a83a7284db9f579d792f855b21cb2569690d7c290802dedad31895a9fb196b17c681dda835149629c0b6f920d39369383756ce4cf3a1e0abef2c071a0894f95284aca453dbe7efd3f
+HASH: c8f99a471202a47e
+
+KEY: d843a02999da4224a30a0adb34a80268
+IN: a3a3d15d413fa2f3310998ea550afd2dbcabba608513091dc4e843421960a29698731aabfea889af810df61ce468e826197b5fa275ab23f6908057d8eb2acc0f8c81efc9685231a198a2090d30775fcbeed564cde84f84e4c7e524f16fdc6a987814369e6aafd206e7428186339d959f084410e41c3bced089e948dd2a0158b5931c77d95ddfc7197ada7d3b212a566b7dd8483214a7de9294fbaf78fd00466650d1a244dc0c507101f05ce6d0950af15c51d21c9ae04a93cbab01307e3324d4b834252c279c28f507006646abf701bc9cc403ee9380dc8e9e2c
+HASH: a0f82e3e201341f3
+
+KEY: 5359880d90b305690c64bb649086fc2a
+IN: 16eecf2748419995e64f241eed4d538db76e06f2e4f8310a982712020c02765274b86026c6478e887a7f557e56af5e56d43ba416603d776c9342244425a05230d5ebcc501f4ae8fe5bb86f8e992ab4aa082db872395e320826e67c606bd933d803afdb22ce3bc4da0d19c5a539278f0f51ddd3ccff495e957c3ad0e08ea8a70f3083f40b9dde9545cbc47e73d5cb89ae8f8351ea0ac46e9aa7fc4712adf5722a7b26886b6b5b594fb05725456c6d948e58302cea70ed3f2ae704215216966d087a366723b7c600977cbc974c027ad2d9409c94fd9988d819735675
+HASH: d6b14df3cf4c7f49
+
+KEY: 0f0ec13184dbca8f609961b6b9fce767
+IN: 204174a8e023de7e271ac5db5fc7ad543293b4f17e1e648aa605617cec55abb9e53796d747fb13dedde4416321442fbeb94e0afb5db024dc3555b7d7d62cef09b3fbb3f7f3ba3ea5369984818f62cd37924c2089b03df6b73373e6b45db6202f7ea04931187fc8b6bc395dddf1a9156e91073bb024ee7b8150646b69995ebb47c8f513e0840ff2a4da3cdfbe5ead7debf930be5fd08ad6a8c839677a92bb6d2c7ade3bb15a07ed1b935113dadde7471fb366aac6b3d57c425edf0117914f76c51b8b590e16640179f881f784b909afcf79744a869368b7318d47ae92
+HASH: 2eaf85ca296e71f6
+
+KEY: 26f952322129ad96deb1742c4cf02db6
+IN: 6f104f1a7dce93e9387b9e8c5fbc819d17b8912d86e74ae98b499ef9ea195e4a7bef3ea9920b7f250c69b77eac0f9c9b6496338b978f6d9edffe36194a450920435d608696e38dccfc6a95aeb48d184d7bc98c5e94a0b60edd1125dbcfc34846384598324eb20b037f3394215ec67b8f24bc7cec72207ed36ea3499611a67a1673e62ff6b269689626817f7a2283e5756134189d9aa74ebdd2527a28cc9d5fe41b7c43bf25dc691cff051588adcac559923847c45aeb4e2b3d193490ea4bb69baef9d4e9df8ed11ce2ad1e59bb0788d1c4ffc4f1578218373a6d28ef73
+HASH: f1cfda7a4f7e71ea
+
+KEY: 7b0ffcf9df3ac2b8921489d0663a6a41
+IN: a11ba4969f1e542b4fbf785b1d626a02a98244a6dc3cbb4b443dbdc700781abd74f27b61af16f2bbbf7439454ba11b317e91348195b75bc72bd84b6855552befb8bfe2c02917a3a8392d689e136835edb24a32b973abc1f2ce2a12e09d3cbb42bf06a73d91df928894768111261264470b58d7053ec41a0dce1b752a8bf4e3f586a41fef78e75dc34e0842db1466a7809b41492b6f65ae1e204f44861fb0e9ca8d315b0731a476eab1a6824280862314c45653da7a9f5d4b23496aa7d0d78162b66d8eb0350e1f163210c99f0e1b63401eddc576a8dcc6a99b204f21c161
+HASH: 0c6d32d8384d66ad
+
+KEY: 127e0b96527d4f78bbbb561ee2f94216
+IN: 883b47f04789efa9dcb466454c40233e4f4d1ddaef746069110a98f023314bd24f5a83c384ca001fe4e32430f7fd16d60aa674bced1f782c9d3f2b9be7ae27eba152316619fdfd9344127f3e95e65ce0494ca9a9aaf86a5c23be50f2cd33fd36431b44e1b86706adb892930be8a5999295cdcb4ac4a7a298f83eb60f239f7aebb2c2bba0cd0cba3a78431209d16517d9c856cdabdd08c0623548a54046e482332e7bc980de6cabff201b0a3795b29f178965009fdf297dbf6826e4cfb8e8db44fe7c825f2e22836bad8f5cc6feb414d983d37a4e8ea9afabaa636181b0f2a9
+HASH: 5df3435199d3acf9
+
+KEY: e245a39e5f7900351fb68b658dedd8ab
+IN: ae84ea795dd308604a72c3dad429ed381664c81f5ac779548e3a1e5b06e93a704a53feac37e62720800bd12fbebaec2e4bb9aed6f5455f7a9625428a98658a244d05e24365f38e984b08f1d8bf7956d92c8dd384eb359d8804a4117e3dd3eb9e77dea725578af70e99fc04005792b2e4446f5800330a82eb7ba6c2f73f8538a5deb50c07f63ace94f924de49aef68ae81dbd8031f6763d68648a4579048944a154b2c4800e02d3cb283678fb28d8b2a0cbbf7f67735263f82b139e0c91d333a9c2efe3535900b7ffa3916dc737a79f6a7cdbd93c928c0c1f3c6a71bb6b1c0faf
+HASH: 009604731001be85
+
+KEY: ff848cf4962c85efa5873d63e0b0676a
+IN: c9783e9b655458a54665ce596f2f507ee53711e724b2fcdbb6a00555e782b2874f723eb9b42c9f05f81df2e400394c18f9520266deb0b39b3d7080d2609d498eb14c2d549fd5571abea40967901ce78a24510d9725812a099609bd23e8f6e1f9c8065a36af3a47aa6b0e31f4a51a13dff8a0b015c556c198915698ccca62093570e9a00fffbb9062411689aef2e23c7ca2a00637ea413dec66cfaa53f6ed0eaa1f7cfd047eb62291418f0992b4020b92574cc1bb16c8e1e0007e84f93f348d596123c0f762dc88eaee3e5acff070c3ad765194c65df5e1a09a29f17528886433bf
+HASH: eaa52c9d937058bc
+
+KEY: 0edd6290e53e244919303e4caf5b6a85
+IN: 005cd6087242d71b5c2824016534c2844f8aba3aaadc2754b5248d2ecb19b55894ebed831f2882485af9676128d7918b8360ac5011fde984a712d4a2966844fef7cd106c64f55ab7420f85b61b6be4a29abc8588f8a88980a61fbadb8501abd4b8713ad9064cad8089755eedf9ed3c60a920b0755a1da89104c95396372468ea9f58450e82f8af1129de70647b72fd3f5f8c0dd38546e140ff2c60b364bbc344bed76ffc7611b69ac958b75b626b3b1ba3334ade07a1b651b4c16877d9e3fd03872d9ee39d3553c929687b31636ddb09eca10dd4b780bb7454aad77b08cf261aee58
+HASH: aeb231e3d8dc7929
+
+KEY: eff94cde18e3fb3ae08782d7417bb658
+IN: 4cf8ef9b0f992acf9cc2616eb3a37a4947a8ba33332fe3e52c3d574c60f257f22173c03f09d7c5dee536c9125516b834877342d33ff351dd036726cbb62cb9aa711a46023c88f912f3b9edca4d0062337c54b811e235afc1f53c3b2ad75af8ff680e17fac8edd9af5ac89ed27d6a2a14d03e2cbb5e3b9c600d3825ba2b1a7363fb840aa29135a07f784dd28edcc7f2999d00c7b192f1dfce15b253e3cdccb36910f0074518b46d03b22c02645687eb19ed0f35c23f21b9c8ef504d1f14cb2ed6e8020f56bc18a0b301dffe80b8f10b8dfc5ce2ce6891bc730981f2decc8462b7238d4a
+HASH: a252d05a57be030a
+
+KEY: a98c1a0a3d066da10ea4dd2531b4ead2
+IN: 98ab505e3aa1551a94a0cf971d3263ab8b65136acdf76ff50363bec0c44ce7e0cc7cdfa157baf16798297cb5e6b8da1bdeeed67f17f8ac5f25399aead8323cb40f78f3e8aa57af8fe42a0a5c54bc40263750a20171b485f9528c6908c081bc06ffc370293c286c105fb01b4dc3a748e3ab0bfde3deeae6462577a56170d735aaf91bbc47df478f7422a1512663b5cfa60574c005d4f42a944c5a9034553ba253d4a9ec91a4c13afbb6fc2a33820466b3566e48242cda460651cf05d2bbdfa6f24700971dc8423e697aa50bd74f6b8517e36be876cdfff785f28fc4057f754426cb63f06c
+HASH: d2c20d0e5079c609
+
+KEY: fc4f980ee86aec735579dc60fe34ce08
+IN: b04be0eb00f65857e72182b6c72e441bb0fce911b147a18e920e32fe4f68b34c401ee1553cd03bcd9ec5e04030e61c9e652d1ca028bbe3e58b1b673748b8be01c135a210888f2b1729dcf430d3762634e88282ed383a2c72df11234f3d6f1c664bece2789016020695f64b325129fdb41aad72411546219708967523ce612c23fcdbb5bd96d75dfda8331033c4dbc6b4a6eeeefe3981913363420ba4e1cbecabeccb1caaf737ccaa3fc23b7bed2d19e9eff329b5e5d7c022951eced50fdb4581b69dedfbb73dfd7ef0f1a2e20fac4cd945a62459bc09499557651215584a2206541a874d08
+HASH: e9bb0be3d8c71cdc
+
+KEY: ffba67ce7af257ece6904cef96d30c3a
+IN: 59db9d944785bfbfab97d99707be5a2bd6b73fd5b566d030e6d50066d41eb3999cf4403f41a5ce5ff43e222c72b5f55d9d97e4e7841ed4630ad11eafac49237978bbeda43d16a713443f21747ad304af680d188fae4204073323c41b9a2ed6ea43649f9155f4da34ac26dbbac19823cfec2916f51093cb60e422949c105b051e1344ec1cadfbec60b16da6ec4e1704702f62bd312b61e572463c9971f8f4e57a835e2e13c938281d5ac256ecf525745ea6a9a31b4e3057bbcdfde132745976e6f37d84616917091bec6c5596705e87f931e1ffacc81fdb16ef5d0cf2395f01d8132a2a726853
+HASH: 9b41935860e05a2a
+
+KEY: e7b8fb02585eac064789f1bcf136ec3a
+IN: ab7721278f866963614de30544ccab492095c464773362883aafad7c6b314091edbbf3b031ed77c0bae0280bc0be2caa9930102dd42b89812617234fed1103978441b72c554c8152af11cf0cf610cd930baea47c50ad28dc46650fb55f5d82257d6fd070f7055f488915d5d39e2d0ee5fa9433ef942172d044f2caf9a2dfda8d4db42fd8099fd9427992db51570078943ba5033e2d36f8f5cf6f8d5044144c23bfed90f8f75bda025931e92f75ad0d2cf017835bf36fb77deb888f921867a1e3a3e39ad6d0166dae4985871b0001bec49fa62d744d20a87735c737b1860d5fbe068e7e1ff31787
+HASH: 65913fb8e4f1ef6b
+
+KEY: 7d039acba5bf658f2a8de71fe1f2808c
+IN: da6da5c841965e182434279138ad525350dfe97aabdd2a6bd0e9b3b558ee366f6b4ebf993e8b8883b2dec949ab2dcbb5afc987e86e6f57ed1c7f87ce4f672b7c599c7801dce3fb3b554f2baf70269916e82444c93ed3bdf32b4737574794b4def37a289707bbb4bd0b2d9d9c0350ce6ca9e8c1ecc60eff91e2dcdc6acd20595089e9971eaa4a486bf47c31944c98fecdc320ded6719276c29f1b785235eb90c1d8548dfcbfa1eff50f0eaf67b60f301793334feb02ea4b7ba276b870eb309f67a0c1df94a4695faaff50a34802c0e2697df2777e4b56355e02fb20b0055f477a4c8b67ea92cdc072
+HASH: b199822f1811ec96
+
+KEY: c54508b0162a1e8b0f79394dd51d3cbb
+IN: c72b4d57526c9a62c225aaf4b200cfd58ffb6bb43f081582bc2b7d18c722f83db54718509f22e3f561039d6ea67087be8da0ded3edfde792e4aa4e6b28e3f5b92634e3fa0655c9fcb8fdb6463a7b94952c8ec26d1b36363022ae29d3befbc74675c65ad84105ee22aecb291ce239bfd99e6472662b6bd211667079050e4795a52a66a690e0552c4649df2a872eca47a9d9df5eac593d3db636c0eb9a382d9e53a23a2233fad1bff227abb35ef0081e2d998b7369a3c82b1906cd6f089404624ff98bec2d3086dc784399bc31e91a8c6e1d1b9d5362d552204fd4220fe950efc4347e92f97a07bc5169
+HASH: e974a29e63624bfd
+
+KEY: c7e90e3bdde2cea53430ffa4d24b864a
+IN: 024c6b2a7e1c73e343484ee28f186e4df5209c1a1ddb6382b32f6af94a7b92f12d1d4afe1a046cc47fabff118b8cf4d3455b8a9bc35851a12e0d8f64b7b2aac84f9d31264955c0098afe2b41a461c5c05758cdf32b4a2bd7d9aed617c443afcc027e44639504706dc2dea1437772dde830885d6b9f8027ece4a9b9fc94e636b6ad2d54345bafa989a2210215dca60ce1ffeb7d999001bd1ac947b46e88bc024025241e1275a68f04046fb2c67850fe73b3d968c56e6f3e10846a33f088cf5576c8819d983c5c1d84e9e0eaeea4801d4800df0d575f4bce9a4b638cc46f546c7b18e5c6454f676fbd3425
+HASH: 173022c25c47bbcc
+
+KEY: 6e2d1460bcf8bc398481a0f14e807ce3
+IN: c3928a0547e7e1157e794f0c01be408412011794688fb8e0bc40508834e92f65930e91c3be3bf16c4f1980aa9261ba332e1c858397f3bdf0ae7b568730d6e294e351656f0a3e266ff119ef6dde49f6b4e79428bf00d7fd3a984f7b0e58272614e5d44d8f1babeff6e6152654ac264bd5de4d39f33656604f32576b7f98a412b822292a838e6a028cb6ebbe76c393f413c5ab635d9ffa568e371169d38e9fae4161055d87e2337fdbfea5f63553122d95d74acf9059a23bf4c5cb21189facf61e307adfcab2ffe9a00acbd7c8c447a301006863481caded3b0ec5b596ae1a394be98dc12454f6238fded0c9
+HASH: e91f147c8967bfff
+
+KEY: 92154b863e95c4b049170adf959758f4
+IN: a24952cdc02ef6ab70e588ac34dc10ce71b79441e11c601e8814631da2c8e0c53b757d9d8642139643bdc9a266d957d82174b160d56913abb72d14c9c3ab9af9232dbcee2b28949906df9c58d31aee3920faf7fb15f1b5047d88cd78d3d4f337a85dd7aafa745cfcd367ade881ee88b9af1835dbc8ba2d2c9ec0744937eb287f9d81959adcc513d180dc0b10503a8fceac08c51e0b479d42279d743887f51c7d66f4a57953f5123940818ceee6311b88428e626f1ffd411fb956683e02fde1e1bff712ef61cc0181fca7a29630de7954360037ba7d9d3ee2129ae6572321897734b1082ca8d4478052a5a1df
+HASH: 6e651cdf9607d1b2
+
+KEY: 5e85e11e3aa3b90a184e2024fb78a2c2
+IN: c3ec1bc51d40d05304ddf0bf8bc7b65537cc0038971fb18786188c7f621cabb636d27ceefe189483d6ab0e5ec88965ed58d32abf344478d67c2c194dc8748ed5cfecc3c47d7f197119c92724480e5fad420a5c80526e5178f25298ca1676f01e45cb72544d4f8715f3e495312fef1a6b7ac3dbc5703e580e159355c608ce6e6f5ea516d5c0b62325be4533fc595793c37ee212dd921b1a98247cc761fe3c0d9b8e9b031d295de0a910120f6e6c88ad01ae2c6c14d3a24b01da0c7ce0ad98e94420827b4f05a52227330d5612f8af1283b20cc89f3a1dd66079b4d5a42463c94b472a96b2f5380c8ad73aa2c897
+HASH: 79cafc4c1f36d921
+
+KEY: 3e9d8c681b3db54b76e7e788e3f9fc77
+IN: 8f76eb5689f2651a493e9b3844915e79356ace323dcda0971d1b73749d1fcc1de35fb610790a47d2c694c8772aa66aa1f960235a8fa1ffda2be3a218a0e95158f0833afc096301e2a471f949a35a011b7fad96e4df097507a8b4f26534971ab1c7003fcd49ae2b453f85b83b493877412f6e7296ca1b41684a05432f9bd127c158bba9e1961c027c0e718ccd130176da0d846fec138aab4be2f2a353304e20e1e733802b0a78bdc120d5e67dd75f1a592441907d5ad4301cea73da09c85e0098648fd020812dc0b7123b57bdd8f43eb330cb7974e3fa304261b6cad45fd8c18f771e46150a37a97fd459d9a36425
+HASH: f6a64307c9821557
+
+KEY: 6890a33ba93b36f9735b14da070629bb
+IN: 8b999ad629181b9d8fd808f7573a015b621243d48c894ac3083c3f01ebd793a9d52477c604046ef3465160a53e1842a4a0ad59608d72649a88be0fc798ed6498fac2927857dd35612ca3c3c457a6b051938d30a88cdc6f4df013e038681610b6c418cde5865c59c585f0f85234011fb1cd97b4d845b33f42cca4363aa49d4a72b481984018000d92d15c9f1d37d474dadcfa7de5c6cd21a68ea3a8c494d0a61c1415dc2e831f227f0778088e70b57cb8e2ee7d4111f9f2dd5130ef964894de7dcbc4bfe4cdbde5cb5ae97006bc7f12410d1610cd5576f4e4179f9bed7f7c8cdbb4690d2ae351b88ed047ae38822512
+HASH: 761ba884dec4ac03
+
+KEY: e78c745365b61651ebaffe8c42d46f6f
+IN: fac2039fb07eea055fe1788f9002ebd278decb65fcb642977109ab11d967544118c20c28c0f221cf97588e709b0c1972770a1d9dd90e2c8327b827369c7df92ae171ff5ae67c3a473f4149d886fa86d306039743b3709ac4bc03bb3d4aa6ccda74b5eed3b1e6206e26f113458a442a2582d43776ce4792d71882fd03497eb1dc4368df563a0e81cd2ec4fa0b07c7db846af0dfbac90f36b366cc5c6afcd347df9267c50d3d7b69bc5b2ec686feee2adbc2aade8cf2b7dedf70dcff62535cd37c9ca232797f515cf8f2e18bc1d061ed3133bab326988fcd00903eb3382c5188257823ce6b19b5030532c744ec29fc4c6f
+HASH: 302751549cc2415e
+
+KEY: 0ce7aa35b6a163f25444f387a2358e93
+IN: 4f3c83824e76591565f4d76e2144fbf598ea40e7d0f35ab8dac089548179c564732a8e9b047bc710ceeb514c29c8f0bbdf29b03203058aadea827e805b5b06b15d4fa1be0d2911793cfefa5b77631704081c8e0473060819d6c0a8cff55899ba8715761ccc01b6771b69ccf41e565d14f1857a17b57da20e5437a7f854d7950d659476ee55368c375a36e178097d3c36d3453df382963301e0759c61b470d14aa5744e6cc92bc1968517ead59081885baa92117737a724a2a5c64b33cc42f46dd7df800148b104d82e347a9872a8b42f7f8a24c017960569d163a21d1d99306c19b08d1c7447b56f0b1326a5983e21b51a
+HASH: 2583827c53bdf070
+
+KEY: 0e1a6d15be1f656b7a21a344f2e0d314
+IN: 7514736145f300344d473bbe03f1190bd65bfb8756ca358e2b9004320b1b7888b37e699c0b31d30793e1041250165ce39d1fc4d88a4ffd31b8b067436f659b762b48008d1508e699dd652c73da4557dae02d2199ec8cf489e2622622cffc36462fc663995530d5184be2b2f2bd107da854ceec4bafa2c6855c4b642b36cda7dd71e155757730b3fafadf0c974cff7dd965127349d7fa287091480e774e442e885cbaf336fc5d6713371c8bce55757efde52321dad605b1dd0b8760123d756487b2c061ec33e339c55796ed9121e9d5090041065a3a4adaeeb8da3af3f11cebe81a216f2332b9f9e1c1d9cb8a288c54c6ae01
+HASH: 023f403d40ddc4d3
+
+KEY: a79f2d6168912675a055766b8570075e
+IN: 8b872f2d9cf5fe9f7b9844e817fd39766e2a86bf6a463e15198e2316bf54fe6d9dac25f642d0fd828aa0ff836c863082165cd44b95c40139e86165281ca264333b470fd3db7887db448056ecdee005423ac2f1444683456064b37007de2143dcc5b75637f4043f1841ad8324b0fbe1fb8af2d69776986b057f97d9acbc44e1b11ee764b03c3f5d6f1ccda44cef0cac2e90ca4c5c938120f0533ee069b9a6fd698acdf7abca384948538e9028b461ea9e8f63369b070602d31459b69dd7e70473b5306a99609c9575cc438e4b95bff7011cdd8a14bfccc2ac32167e2e2c87e2571e4680ea71889197c3e14b61bf46ddecedb6a2
+HASH: 2da234639b4f5b51
+
+KEY: c0ed7efd7c3a530b33c0a8c9f75ef3b3
+IN: f3348518f9cc5cb4789aa17cc376e9f0f0a813e0759ec41c73b92aac9bb61dc0dc257b4980895e52483453dcee6780db1c81c3ed797c9a034c6fc2ef9b9e87746d2526c0494d3f68a89b92745cbd7a1f6cd7cba08c7572d20cd325934997f568b7be8f487f2d9fb1f377ae12db20c4175ba7a7217d6763a40ee6fd1ee8593b49f22b16e737a20617fa3d369971f26c669e4a71940c8be44eda90c2326f834669485643ae820eedf05385451ab4d5a37f175996e5f19bebbc45d3052f5fbca7fdc413762c0cabeca5660131bde115c4b236c0e4ba137f9fc0c62dadc7cd40a4b1e81f41508ba1ff86dd0c5437207b41c1869a1ffb
+HASH: 525c8c3503737a42
+
+KEY: c9322e7a0fa3d3cb63448c0cf91a710e
+IN: 46c7eaab931b65b4f7734440a7da94bbb6b7bbed3c875edbf086d5338e9fb89c9586eaf834a9ec1522de16bfee02ecf525bf9ba94ba3723af970b958e32212ddbe9ef1a28005093cb725f95fffe8f3add2d468d0224345d75acc4e1e3db00c0f4a219904c39931a7d6fcef5aeed12d67c6a2c05a41934a04c68c9a5ec4bb2b7328a1650c1841fc14fbacee0b570e3c6d8f6383c6b37ae35b1d7118f45d43d55a3b453d3698acd84e9404b54e2fa8b063ee75d1fdc13f984a79a9b555d190f108a0118d89f95285f92d1dac17e04dbd51aebbc40683acaba35552a569cef829b5a3a6c41832c63aa136a9b707e3b037c80376175c77
+HASH: ee54e3909a0b371c
+
+KEY: 49a26a0a06a3d9aef4fc6ea1a9805c17
+IN: bfaa93598cd0e67d40090305ffe8f20d52b1ae7f87e2ba8ef15cffeb8098089805161159807562fd9d47c75a2d1e00994707f98170090391ed72163f8311e1e578ceee03601ed10ea6257668330810b79564d30d87ce3e5398af7930ae24d6d9988dc18b1494e83b60500c728e090a466116e6685af67267d83f202f67ac31e8669f6dc27afbcf4aef3c015f63bd0106cc2ff1cbb98e9799642ff744b49e6dc34aed31df345d06de561470afe82e233739be2abe648a3b02cc5f28372b5b8dcef13162699c08ff46b2bc236f63a32f7a5b3600fb09a7a77f9e3ffdf9b0e1a358199fe3ef5a790cbb6ea022aa7f174436a686d57183d3
+HASH: f803371f2f4d3277
+
+KEY: 303c484fb49204e725e2d2951bbd9896
+IN: a63ba5a033912d1dd5c673c2ccd53c5e6ab5f2bc39bc7ff4f5c35c0b5d2c4e0bddce4229fe836afcba360bec541aa5253f9d2c54e884ed07eab9fb454fe5243dd16239b8c50b5801600c6534d2dca1eb61da95426837abcce212454ebf79d1fbe456d6ee12eedc4f1a48a7e4af5d9308adc4f5cf4c54386b0faab7891bc00e177aba304455eec8b6ce64445e1bd7f71f586f6cfc200eb56a579e8649b8ead10786588becb7754d76de920e1fb57882649e1443e77cac77a227e183c7756557e3b36e1028f4971fe47e4dc18ffa1d3d24edfd6bc4bdd6e6e918029b4070980c6f3f7b2aa96a7fff3cade78e8030ef576491d7114c6c28ad
+HASH: 7e9bce366d20bea8
+
+KEY: c37aaabc6d0db4b4236d30ddf60ca347
+IN: 5a0567f73ac4f0a913051bf056ef2ea3722f1736a812e78524884da840dc3b5b253f88a63d1feda1b4b09ca8e6d63fcf7b547f6d181dc1b6db7efc46bb5e2972aa56ec3a08385e9b9863692db11c615b3f2f72d294cf57943197a5248d88efdb2a4c00cabc5eab1a615f61bed515ab0d6ed652fdb41eaee68566081e89052ba7f4cc1b168676609dd1c54821d82461ce36e660bff3e9f526259773ee811e1292b6fe1d7cb2a155c3a638bfa7b1b7040ae1ab8bf31ae2afcea6a3d502e89da2fdadf74bd6a9705ba6f2a6ca8784d8b6a25f3ed874371842244198e596d0dcda9823dd918eb3677b23ee6cae08a0f1c923ca9580833b70dd02
+HASH: 03b55be4e5f448ad
+
+KEY: e3c6637edb2431c3d7e19d789b9b795f
+IN: da494da4807bbf8a852809b22467a8d386c55e9233125cb73c1915fd8b0eb94f683085472825041ccba2ea14cf600f4f03555d73c8b239c9bfae0d2cc1e7641b43e93524a58fe6730cfaf7e62311250f4934077e3883205c7f53b5719be2c2382659accbc9e7a79839c1156c4314110889f7123f3876fe8aa2fa38fc5a0c39fe0b1e8e5652111f0b420c3bbaf082f50454abecc6f8ad2bc37f655965a972cfd8ec726d0169f916847a5065321d817d67c243677346b96c6bdbc9ba8e7eda16b2ba56cfc0dc8336b31eb2bf8b14033fab8dcacb612e556743b28a9c24ddbe58b27ca303bd504ba2be547c3b6893b9eac8e21c5c75e85fa0530e
+HASH: 135f12ed177a486f
+
+KEY: c2723318c7fcee8351ffbfc223d5654a
+IN: 4bc1f8e3e5a6369473829b634a171988c009dcc4b2206036ebf419f6a722710cddec29901c62e892f2ec519caa6be7771f09065ceb21332e30a8ed662ba62af0b2d6ddcd33095dd280f0ba22122e7f3bbfe8809a6eccd983176749a7d9f086df6fd895494d3bd225cd0552884c90cd6915f57185af57dde6b41b7449a89918592c6b9b9944ae2c78d3a65d74fa9174d395fe5e33452997286d8e52853197a01e159172c663e2fb88919a03c710c9967c2de00d55622b854d42d72eb99bdc4b3324b1e022b6c383b26621fe81071ffb5e94193ee9847b76b98325b42548cd1728b77dba4384284881b69934759421f7a259a8e54dc89f7938687a
+HASH: ee2d28c3ea0b7ce5
+
+KEY: 390fc266fd6339d769eeda067acc5cc5
+IN: 53fcef219449622f20e5978afd27c4568e130d0c1db23b4d94c110069353aa40932d5f0b1fb6edf5f54ffb217516a094f0f11fef692ac66420f7c192c1fee13892971cecdaf923824c55679a379185cb7b74af34a920e64a1fcffaabf699c1ee057fd7f64e73ae45597d4dced74f59926f9b7a989a963ecc12f0e31143fd89f57270b272cbb8a19f106e83a134f1e7f63a6714ba36b1fad3cb7094d42d36bf42f0e66696972ea0bc7167bf718e4ed57ce0735de11407e7f06a171f7c7ee5a2982cfe34db94aac9227ad759bf66c45e791b4cbc9dc0eb44596520008b435ab78778e057960aecc669645d51326a4533760eed682dcf21d38b0a7f85
+HASH: b5fcdecae4d9f22f
+
+KEY: 4b7f98e24a91752f71cb2a1cf4133b7a
+IN: cc21aebd00b960f302d250f64241d428c2774bd70275248b39619a3c428819c7061de63b44c2d9b865ce0f2348de73ce7d1875fdf34bb3b74690bf03dae40b4f8fdd348196fb5e265518c67d643984b3046f5dba1aa2a3308b03009e7fa67263bcdcb4c1ba6721bdd0475a9f3a202492f24e8c40216ab4a47efc4fb156cb0d3e8491128e50214da90ade66065f75bd3087957b547d5421a9f4502da385482b4b75003a05e6f35dc2ccff7835782c79a1456417c169e76f7de8894ffe07315b7d3787c9209e73a3747e64dc0e2672f3d4f1912aeb1e8fa861a4de41852a8456f619c335568aed6917c8c93c91e2933dd8d8c51229b6bea28f2445dd85
+HASH: b18e5bc73472b149
+
+KEY: 3a0caae8e1afa4bc915f83260c4b69a4
+IN: 541af16c8314adbdec55ed3eb06820b178883ce26aae5dd1ffac1544b615426e5af0d585d7e2f922a228d9812fccef3e0c9f0a78b98d5de8c683bfb011c547af744eebac3d2879cd1ba2d01f39ec8efd116309cba48a7c280a0656a53a68d9a90947124829852c076d4ab85ad2f1a73121f0db1f8de6520c53b3a010a1e9febb37dc27906e4358e00b61e3f7be83d070731c1c56c59f1aa93185ae1c6e944db721b3e389f0d77f2546d8aa32b54ea8f9f6d6d2f2c3bea3b7e4044623745c06f1c99d4ba71b7e052112f30823b210e09cedc62ed1a8a38716f263ed0f5cfca0bf760c5005b6bf5479c7585fafeb34fd9742a909414bee7fcd0b7256f961
+HASH: 33e708ecdb29ab35
+
+KEY: 70e8b305ad3d34346b7f21a84e99a9b0
+IN: d8f7d6548477826bbd0a2a5aa8993869057a032e7631aa4870e3a2dbc80e3c571e516519780f0ca5b63969171b5bf2f48ff15e922de8e438fe56152d214e828f869f309d711bf00f41b52a9181984afe09c526fb7bf9af7a02c52f77a419f07b13a2c06af5a1589d69b56440a7bad5290ecb3ebc16314e016603c5f96f2480054caea48adcd8eb7d3c79bcf7117073eaac802243c878a202ec5a1c20360c3fc97c688d35eba6c894a183377a9b836fd442da42fbb0fa8fe68e824a00c509eabcfc68e61faa731554f9b9d2cd1bf85c3c59e8eb64a6040f345685ea5c8202690ed62eb9392f932af9107e54ddbd59f150d252a5c3467ba44e1544d889e7b0
+HASH: 0af2dc6739b90eb2
+
+KEY: a96ec37c773d908b5cc732eabb877b24
+IN: 6baf8f4a20f45bf086c0003c0c6e3837cbdbee6f861d27417547d06e21feb53703fa0f896dde73cb97671e4af7b27fb175d802bf3d941e13172ef158aa6f19e27a504e46c1f1b1dac741bc3ce66b73f46941821561be24448e37a64af54c9beb16da91ee8705d13b2882b9c80fdbe249e1d3b6e5c671ee524d76f543dac473bf83cddcd95c07e82d70ed4f0d86615e6d3776e6db7f740ff7db55a949193734b2b9caa48dfafd8bb40bd8cf2eac7bb6462e92f2a0fc101c7b1fef491f6a16c1c555df9a57a1f7a8e70eeceee14bc6e81882a8fecc48ff9b171b9e8915882f281925282a479b8239bfb48d8cdcc56c22294210a484702c5a8e4d4fdee2f6223e
+HASH: d10db578e9b44fc3
+
+KEY: d5f074017db58f6e68d210b58e091d7b
+IN: 319a1100ad4e268dcc7f3ce71fac847336fbedb9046c1b1b433c373385326fcde6824b3b1ff34f86a0a27b9216afe3145945eb5f7aa37875fac08cc62abf01e2591f8d8109f102214457193412c58a393c4819812ec9fc2079df042a3f57d172d76a8f2709b942e79b3ef839f2f51dab93d225e6eaef30b2a67324481d60a9c5a215ada44eca05470337379dd01eb0dea2cd14bcfd0991d0bb11a6b0fce97b4e51492fa665ee7e29cbba50a5ce0d50c663b16ec8260edce17d548c6d8566cc76ea7270e406f9c888849d15197b37d30c64eac4eb90f3101ef65b3c1493370f8758e7d954b41a543e520394f2da5229efd6f06ca947bfff71b11425a67229120f
+HASH: 1b460b968867e42d
+
+KEY: 338962444ff46d8d1c4ef2d14849202c
+IN: bdfc2290fb71a50aa63595c3f11ffc426539678601b31bae30b1771435e3199668980adb627c738ea28ba458c0179e48a802d81afe0b2d09b5c3efb06808a22d73173049781c0822d780e360e442619dd82cfca21291eb0b793eaef44202b7a5e6c30505ce4e026bbf724ce76a05e827c526ea99bef552460abff8058142e11e1c223aa0cc31bfa2e5f9184d9a0fb9533facadfa08b5da0a7b65ea1758675bbeb1ce2faa11ce20ade70ad5fcf88fd253c75e5a2d0ec79f3468e5056309b4d46e1e96ec811a14cf6e64fdb91a044979a86b571a9d701b6663d8f24ad77c4a514576fa942f45970993dc703dbc68a06eb688b9f7e87fbae04019d54320a26a5b7b84
+HASH: 2529cebc7a6002d2
+
+KEY: 94f80db0bb92d9f844c8fcc43ce0d94b
+IN: 3d09d5fadd1e79e648ed155dde5646d2b9b90c9103d4d7b4d53d5ff184033fb17db63516b70d4407aaf3be325d66d7196d08e310851c7a9f6ecb6d3a73b7ebc9bd604db7bf93879e17c2e1d7654247295ea02b97a60a3b85261119d021ed1f040f27e456f011cb46dad312b65c1927765cad2c56227d6aed2a5d7754a57de58c11f185aee88e38ae5c8fdbfe6242eb3dfc7981592556cdaf21351648abc64667998c4003cfb8376417ff98b506c0fa51dd2a152d00f34f6ab58c883362b08c986041724d360f68b94d8d071e327ff536023c98313f09df4ecdf11e16ecfaf3970ad682086ffc7e43d571a96b7103e9eb80acdb6fb8f78107a8042b2c8893b1d2f19f
+HASH: c4746b2d19941e56
+
+KEY: 995882b99f51c4a67f5b55f90e2a9aa2
+IN: f640e51da483aadfa88812d5851ea3a0d9e605b144013ffb7f74df26cae9317fd90233e2893575d5e470df2d80a2e9f4e8f6f5e5f885498bdad37d1489ab823dab96832d48c7617b255d40716d54dc45e9bb3d88f908d573172486d88c698496faf792696ac1f446419f6459866cd4ccfac5359ad9fc8a7bf85f755d08fa5e44f4b0def68513a39ddb97698180592baa03e37220eb955c636baef003040d753d5b2c307edd1b7a24ba1df13b9bcdd72cfbea7b17aeea59c604e509f511855e3784328479de46457d1ce706cd2f9e937d71192ff215733c62a205a6f08b901e2521e1935ee33d524c3d10c73c0a593acced88f73bf6624b15de138076569945cf2b6b11
+HASH: b24f340cc0fb1d2d
+
+KEY: 8da14614383bf855cdc281b5225515ce
+IN: e17c973f19eb796bfbc57ddb9601806998a5e97f18a3eca6a1d20c3cdcd109815cd8ee13981858324ab89e8b30214e3251e6f308648552034545f7d24ca74aa6de5bc1513c20d869a310ad1a44e462666d2817c0cc225a28efa78b1435c80adca1e07683e1792152267649aeb7ee19efade02dbc631b1fc58ac59671ea5982234da90dad42430fbd85fe6527991ccea11bb805815fdd0c8a920dc9e818a5fcf8fbf680a06999c42c33c7cc712dac8badf82059d142c6c3f80a8d09c881182cf336cce2e0e088d0af39fcdf6dc29c1ef496968455e2fa149a65cc57df06fe6830895e2daf352e922bff8cc3fc757161f3c61b2ec9e7d787e1ad976595d27f0f5fc5a04da5
+HASH: 36bd2cce6ed21122
+
+KEY: 70e11b8cda2ab4a0abcb736625145d90
+IN: 7f4fa8fc41f72c8fdcce996949b74560ea9fc7a3c57e502628487144472bee4300671cc1617fa7c76850df408b92020e7f5d377f4c8f410288ff60aca90516d0f1d13969c9e5af7e348ea7f8f9a36b8c69d0645432103f5b93ab560f3113e2d86b35fd6e704116ae5e7bfdf1c29b6d5a4570e2b1082a3e6023f14e666a76af86e4661e140eb836f262dbd5d388c166215ac4f39dd9fccbe3a3a8c58a2236304e8fc0fa1fc535e860e888a0d640277426e2e2a05d855c5ffd0876ee0306cc0c394d064fc45e974d1c19094a1e83d59057bf269f581637f343a9fac376ed41f4d193aa5e3c30141d31391c02390afd81bd32b6865ddd8974a44724217eaadd7ef316e0b4c2a5
+HASH: f6624d4d13833699
+
+KEY: e974c2247093c75ef6ed70632f16924e
+IN: 71f2e0508d7d36b373e8ee0fabf49dfd69638c5a6c79be9c89bbd0043b3bc66abb3618c67299c0afc4e24af8440185ce8d7d8b1d45732f123aa8243a9a0a0bb27b3d6bf59b39e389c15211b92c93bc5aa8582c98fb1523427c84969a65e99775e9d97f382b9ab47ccfdaddac3e7201f917bb64843f2b804801a018c71bb6f632f4521c1a4e9a375d7bbc8a42e561843dd65ae3e7ec0da1db8751960a656876983986bb2be2dadca5bb312cb004eb10f1c608af4afa599d946bdf8bd52102c257871810f5c8a899580287074686738a5823f33e96d3c2a7c77b68e4a21a5ac50e06664944d0cd6bd40ae95a2e21236e671bdaef79f08da52e2a6f65131a2e80c6263ddbeb9759
+HASH: 00ab496891593584
+
+KEY: c6f1ee0aada0c076cb20cad883bbcf52
+IN: 912f5b270fe4f445134d1caad3cd04cebc9c377fd03d3e26b82d4449d4ce1914ea718a6fa8c179dcdce1d117e99f4e8cad2cc0f0c059fd94cd8b3149a38a6b271c7fe1c90943c7a8d40774817c272a9139a05e1c0aa74cfb5a7c3c93aed9303b9579200022527a0911908875e23b1d8bacd42a123fac2342723a77942523b4aa858ff284821e1ecdb82ab2858580227bdb2e297ba3680a1f1cb0d31cb8e6f6c45235dc64f4c3f986c24f9ae3a6cb9d3710d58125f20d8e8c4cf4f89adc091649bd7a33ecb58cd163baed98e7e4f4dc16955f7f09bee1a51e0f76e9575cbc2733bf58bb4be4a4e2a2b1285c1b928aae6d8d9becdbc3723b408fc01acccb95861d23a3940df799bd
+HASH: 792ff12185fc3326
+
+KEY: 5cad21c8c3805f62248b1e1758c9c392
+IN: ec943f6a3d3681cd718f532bea0e5e2839c9425d0381dc5808cbfb273f6730efd52496b0331a711fce232991dd0847b5b6fd58330f54c19f571346d1e100e2304ebf5fff1b1047ddbbbc4dae02ffa3a21f8655b8972f683a33676352908e007ae3d12ce62e77acb2ca8278161d7c6964d6b97332c19c1d1d7288542eec4743731144a0e8a0abd58d0df0664eeacd5e97e272d672e110734c84c075b9ca5ecc279010f22236063a8ed1a7ef31bd84637832ce375944eb672b51edd7b4b248dca106295d83f14f160b63f84a49cb6a0ab143494ccdf5444c464ede3ee7936f821857cb2f3cd7d29da414c10ab7db05e4d0817c1556804b1d3d9c771adafae48541f930464549cd2635
+HASH: e428c0fa3cf4f1cf
+
+KEY: f6f49ac81cbafda6579b81c9d34de602
+IN: b5e3e21c2b2104c50bdd9a96667d460c3a4c0a5975df60369ebfc0f6ace585d727292f1d37107ef0bdd7ef16fe8740e0453501405c2a863289773a4d121b37f01e1672b0339f57fb445f132262f6337f64e9df4ed7fa79ed1c722171cf350f1c1d78e7427904ff3b90bffa1eea64181c4e69440af712e37b7dea95953ebd4ef5efa7bb3b14f3f452e204f9a26fcfa104b80deb56e16e16af88fc710f68df3da7f162e3de3aa9f31a2485169411d6009014578cace09aacebda2e965cd8c4841df7e31704584aafc459e02afc978b812f8ba22e2887728243658b4adcc46a471aa357567505b17fa51fdbc4c5ddc67827cc86f66ce3a55257d00683ed2b67ea19ce1ba0eaaed8c4c1a7
+HASH: 1b44de49c45e9bc3
+
+KEY: 05f5a594c805575eca581fd6e8b16051
+IN: aefef8817ca1ec2ce86d04184ee9f5da020497d3296395cecec10af22696deb65dcf3cfa4a3b808eb1826ff421de79aeebfca796306eb3b39165ad11d8733cee89f30229f75c06dea935045439d5a69ac118c5ca26bf59a9cade2e6b80b0fcff911eaf7f4097f87751570362dc5aa42a379c5ad313fb403b15c9c6c517be4ec28a22039bafd618284b16e12bc38c39e8525b930f5a630d4d595980becae425229bee4c63706fc172f5f9ba6cd5fd36ab474112169c2ef52b2bd72b2ef3bceb8a82ca53a42abcb044b712cc1a6b37a8ca8894b9904076452ddaae641f9137a72987fe928c20b67c2ccaf9fd601fa10c5e4c968727357c4547fc68b0e11b35f4fcbdcc3784c2722eeebefb
+HASH: cd45d7070e5ecb55
+
+KEY: 0546eaf96bc197e372139dac59c9ca1f
+IN: 1e0622e2d160fe532cc789b76e99a5fc7e90078dfa0d3d9342ca8c625192afeacf6935e930bca3e7bb2d7a4db23371b6ee3e645b9c6939bc3d414ddbff363d4ecb2967962bbb1d3b86a476bedd5820d17b86747c886efb34692b87db843588d0db53c3d91f2c305e5d58cb7ab98d6387ac45400d2ad1f2435bdb0c4a76c6fd491c3f2bc049fe70426767cadb0fa897e29cfda1bcf4f18d0b15795de39ca05fd2318dcc8b9afb7a278e986521ad971a236745bb62a680b47707a7dd1ffeb98e8e61edaa038e7c2a568c4a0060c38752eae915e4f99c91d6de11574b3dee5f85209acb8a38a02d0f59f5192a3151ba8073682d191ddd26b01913e98b403a4ce771e5e020ffed080d2dcb7082
+HASH: 079a62a92c61d7ad
+
+KEY: bb1780c25e2afb8c9bc58a3dbec8867a
+IN: ed5287df6db34449db95528d55e64ff486f178d68135dcc996593592a40655c88dfbef41fecbbc09066adca3b3ceca2c9f7be48ba61587ac3c69bb5589fb257bb96c000ab5e5843e9e7257095ae087d084aa7e62e9e3f74079d1c4c10443c9d9bcbcae47c74e72c024e5cd13280f058a772afc379d2dee0e39ec0cfc219cbb2af7698162a3b84be52c90617760e84d3743909bdd5cf21bd3ea3e3e37c3734dda57a06db25ab81ff02823f6d39cf9e068698e1f597458d08a29aefef218b40e191dc3d05f089f133f5f337d98d1c0161f490849782248e915ebcf700d80667b2d0ba3fda022faeeb23ca87add7664666f4637571964d613bf76a22061c9f746f9fb00ff0f5c2d90f648717e1f
+HASH: fcda80db378102b4
+
+KEY: ea7247b6addced42c51c827dbb2cb66c
+IN: 0ec554948d3af7cbc2306d249861f6291d44ab2be6bb26ab629347ac0e892cc902b3c5bf9688e39d826e32609834b34644a8b39c9a91f47a0f24e73a13edeec9e224756f6cff77e1d07fac03c1d803649279e33b2f16dd2d100bd82e9806afadb022700d55f5fc9688193da02dcc9dce974b60ccc2caad064acaa0f1b9e4f24e9074ac7877b91b19c7abbe5bb7f889775cd72ab9334475dddba19d008fdb01ab9cbfb1038a486c76f3c542bac704cf795be628cb83a5d9d128c2b401cf23ce494584cfe5b4ed58d9c906ded22090ffed4894238c6d02835e6180ce662b24ea18fd84d5fcbed49790322260fcf3f193081ccdb60d8f768b6dbfa61867bf90fbb1f27e1a6cb83daad6243079904a
+HASH: f8181640a08f1343
diff --git a/src/crypto/test/abi_test.h b/src/crypto/test/abi_test.h
index 44547f8..a3c4bb8 100644
--- a/src/crypto/test/abi_test.h
+++ b/src/crypto/test/abi_test.h
@@ -149,11 +149,11 @@
 // AAPCS64: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
 // iOS64: https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html
 //
-// In aarch64, r19 (x19 in a 64-bit context) is the platform register. iOS says
-// user code may not touch it. We found no clear reference for Linux. The iOS
-// behavior implies portable assembly cannot use it, and aarch64 has many
-// registers. Thus this framework ignores register's existence. We can test r19
-// violations with grep.
+// In aarch64, r18 (accessed as w18 or x18 in a 64-bit context) is the platform
+// register. iOS says user code may not touch it. We found no clear reference
+// for Linux. The iOS behavior implies portable assembly cannot use it, and
+// aarch64 has many registers. Thus this framework ignores register's existence.
+// We test r18 violations in arm-xlate.pl.
 #define LOOP_CALLER_STATE_REGISTERS()                                \
   /* Per AAPCS64, section 5.1.2, only the bottom 64 bits of v8-v15 */ \
   /* are preserved. These are accessed as dN. */                     \
diff --git a/src/crypto/test/asm/trampoline-armv4.pl b/src/crypto/test/asm/trampoline-armv4.pl
index 30f510e..6118dd7 100755
--- a/src/crypto/test/asm/trampoline-armv4.pl
+++ b/src/crypto/test/asm/trampoline-armv4.pl
@@ -179,4 +179,4 @@
 }
 
 print $code;
-close 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 aab5250..410b59e 100755
--- a/src/crypto/test/asm/trampoline-armv8.pl
+++ b/src/crypto/test/asm/trampoline-armv8.pl
@@ -206,4 +206,4 @@
 }
 
 print $code;
-close 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 569a3dd..4244ac2 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;
+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 8cb1410..5196141 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;
+close STDOUT or die "error closing STDOUT";
diff --git a/src/crypto/x509/make_many_constraints.go b/src/crypto/x509/make_many_constraints.go
index e507403..578618d 100644
--- a/src/crypto/x509/make_many_constraints.go
+++ b/src/crypto/x509/make_many_constraints.go
@@ -137,10 +137,10 @@
 	}{
 		{"many_names1.pem", 513, 513},
 		{"many_names2.pem", 1025, 0},
-		{"many_names3.pem", 0, 1025},
+		{"many_names3.pem", 1, 1025},
 		{"some_names1.pem", 256, 256},
 		{"some_names2.pem", 513, 0},
-		{"some_names3.pem", 0, 513},
+		{"some_names3.pem", 1, 513},
 	}
 	for i, leaf := range leaves {
 		leafTemplate := x509.Certificate{
diff --git a/src/crypto/x509/many_names3.pem b/src/crypto/x509/many_names3.pem
index dbfa042..f15638f 100644
--- a/src/crypto/x509/many_names3.pem
+++ b/src/crypto/x509/many_names3.pem
@@ -1,5 +1,5 @@
 -----BEGIN CERTIFICATE-----
-MIJqmDCCaYCgAwIBAgIBBDANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJDQTAg
+MIJqrDCCaZSgAwIBAgIBBDANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJDQTAg
 Fw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowgmfXMRAwDgYDVQQDEwd0
 MC50ZXN0MRYwFAYJKoZIhvcNAQkBFgd0MEB0ZXN0MRYwFAYJKoZIhvcNAQkBFgd0
 MUB0ZXN0MRYwFAYJKoZIhvcNAQkBFgd0MkB0ZXN0MRYwFAYJKoZIhvcNAQkBFgd0
@@ -560,12 +560,12 @@
 oiXCzepBrhtp5UQSjHD4D4hKtgdMgVxX+LRtwgW3mnu/vBu7rzpr/DS8io99p3lq
 Z1Aky+aNlcMj6MYy8U+YFEevb/V0lRY9oqwmW7BHnXikm/vi6sjIS350U8zb/mRz
 YeIs2R65LUduTL50+UMgat9ocewI2dv8aO9Dph+8NdGtg8LFYyTTHcUxJoMr1PTO
-gnmET19WJH4PrFwk7ZE1QJQQ1L4iKmPeQistuQIDAQABozUwMzAOBgNVHQ8BAf8E
-BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG
-9w0BAQsFAAOCAQEAtMIpnGzOBkJXEBmCsRVbTrg9QgYRlGPG48+cXT2QbIutAmbj
-miF+OYg/bRsQtuqcKjnJYog+x6UCU3d34jaMEfEXfHSwF7xPQrqJm45MXhG3so4E
-+el5GMAS+SKFQK3w8NPoGhGwn82sz4XV6HMG+ANUxMlCrOcx2jh5UW+7ITjdRwJd
-ReJ/JaMpneJdwGFSU9Vn+t7PFb51/pOYqO/PuEANzphovjMVcFZ6mtAQwYDkQZBJ
-Vy1/7bPoNmbKD0GAS6HpS+xaJ/DnjjD6Kal2T7GMyvRMogj5BeZ/uEkXCEhvoaBT
-os1gaqqnGpZ6JSEDctzjgpCtEPR40yiz1wv1CA==
+gnmET19WJH4PrFwk7ZE1QJQQ1L4iKmPeQistuQIDAQABo0kwRzAOBgNVHQ8BAf8E
+BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADASBgNVHREE
+CzAJggd0MC50ZXN0MA0GCSqGSIb3DQEBCwUAA4IBAQAi7LIMyX5Ec514hvjROZ8b
+7i4UR3xd5IbniVSej+PKZhG2inN6aX9bksdda0ddYZeRSHAkNJuoabeankQJ/x5x
+sxBntWSVLCxz6S8NRrLAPKKPBvFb/W5ns57LP9SrLIij9l/NSd+K/CQNTlfcdorg
+4ltPVNwSMp/XXjH6rQYJSbo9MhDoxeqPpv73e4jY0DfGn1a8uwyCXalLjh4EkUyS
+Ye0N7MoUKV0IucrXKdgj2sHgBFqNKJ/GVQ422xZRbYqsyIJ0bPD6Fc8VcqfVrvYg
+lCYJfu7Xij5n3mjQaSYcbVxH71X8fYhhNq1tk+WtQOXirz2EkSuh1rNGU/LT8Q6r
 -----END CERTIFICATE-----
diff --git a/src/crypto/x509/some_names3.pem b/src/crypto/x509/some_names3.pem
index a6d3ee7..7b38bf3 100644
--- a/src/crypto/x509/some_names3.pem
+++ b/src/crypto/x509/some_names3.pem
@@ -1,5 +1,5 @@
 -----BEGIN CERTIFICATE-----
-MII2fzCCNWegAwIBAgIBBzANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJDQTAg
+MII2kzCCNXugAwIBAgIBBzANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJDQTAg
 Fw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowgjO+MRAwDgYDVQQDEwd0
 MC50ZXN0MRYwFAYJKoZIhvcNAQkBFgd0MEB0ZXN0MRYwFAYJKoZIhvcNAQkBFgd0
 MUB0ZXN0MRYwFAYJKoZIhvcNAQkBFgd0MkB0ZXN0MRYwFAYJKoZIhvcNAQkBFgd0
@@ -282,12 +282,13 @@
 BEiEhBxIsaIlws3qQa4baeVEEoxw+A+ISrYHTIFcV/i0bcIFt5p7v7wbu686a/w0
 vIqPfad5amdQJMvmjZXDI+jGMvFPmBRHr2/1dJUWPaKsJluwR514pJv74urIyEt+
 dFPM2/5kc2HiLNkeuS1Hbky+dPlDIGrfaHHsCNnb/GjvQ6YfvDXRrYPCxWMk0x3F
-MSaDK9T0zoJ5hE9fViR+D6xcJO2RNUCUENS+Iipj3kIrLbkCAwEAAaM1MDMwDgYD
+MSaDK9T0zoJ5hE9fViR+D6xcJO2RNUCUENS+Iipj3kIrLbkCAwEAAaNJMEcwDgYD
 VR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAw
-DQYJKoZIhvcNAQELBQADggEBAH6ad2kFE0qGDe3ErMdwTGjbBz3T12dDvAUVhGHQ
-uZShOdPsXMHD2mUqFgLE0iJFeXB7jOSAKtzmKHNmxZ4W0UZ7eMPPogkgIbG3d3yR
-8zBO21CUyOQWChywpKcAou9ji3Kq6pb4+mqq0a5TGIYyGJKSUTv09KI+iHgwteCX
-DHzzhuTs8AhodmNO5K/F9YFWJWvQ1NrwyUmOFEw8/UcljyKxFrP2VEov0fWeiTRB
-Ps6VaFBW7SEEi8fAM9W5kfsl+iWRvwFcFdXGQt1HbeywCu58DLI4uceHCFb+3MMO
-Xv7wJ5UhQODuzwuq7CuZvlxR2tiFoPP+s5fPH0L8MBP5z6w=
+EgYDVR0RBAswCYIHdDAudGVzdDANBgkqhkiG9w0BAQsFAAOCAQEAQA/0vvY1gLA2
+0jrPkBVWte7OHzWVkwq7mqgQPR4L9qLLu7Vhelp4dW8n95s1wCbca5j5SJEGv4Uv
+0fI1OOK7XQeYdNlHBmvMVW47GoBSo6tuYNPI/y4xnM6ypEZiPKkdj9Ar9qNgURfV
+z3s1czip915dyTWgwBy7CTxOlG8NW0uiFgEc9iiDDfQsPwVXiVtxOPtjhPeI3F0J
+jh3wctFxBnAvLV9SsDxpWujM1dd/1SSQ25jKQhbKNtiDAC8v+Q043r8ZGHjRdxe8
+W2tVWH/iz9c+ze0P0ao7LKv8eGzoIsrBqICS86X4Zv5lGeTGaD2osF1oNvmmoSlh
+536yFa415g==
 -----END CERTIFICATE-----
diff --git a/src/crypto/x509/x509_test.cc b/src/crypto/x509/x509_test.cc
index a53ed7a..1f664b9 100644
--- a/src/crypto/x509/x509_test.cc
+++ b/src/crypto/x509/x509_test.cc
@@ -32,6 +32,7 @@
 
 #include "../internal.h"
 #include "../test/test_util.h"
+#include "../x509v3/internal.h"
 
 
 std::string GetTestData(const char *path);
@@ -660,6 +661,162 @@
     "KStYq7X9PKseN+PvmfeoffIKc5R/Ha39oi7cGMVHCr8aiEhsf94=\n"
     "-----END CERTIFICATE-----\n";
 
+// kCommonNameWithSANs is a leaf certificate signed by kSANTypesRoot, with
+// *.host1.test as the common name and a SAN list of *.host2.test and
+// foo.host3.test.
+static const char kCommonNameWithSANs[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIB2zCCAUSgAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp\n"
+    "bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y\n"
+    "MDk5MDEwMTAwMDAwMFowNzEeMBwGA1UEChMVQ29tbW9uIG5hbWUgd2l0aCBTQU5z\n"
+    "MRUwEwYDVQQDDAwqLmhvc3QxLnRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n"
+    "AASgWzfnFnpQrokSLIC+LhCKJDUAY/2usfIDpOnafYoYCasbYetkmOslgyY4Nn07\n"
+    "zjvjNROprA/0bdULXAkdL9bNo0gwRjAbBgNVHSMEFDASgBBAN9cB+0AvuBx+VAQn\n"
+    "jFkBMCcGA1UdEQQgMB6CDCouaG9zdDIudGVzdIIOZm9vLmhvc3QzLnRlc3QwDQYJ\n"
+    "KoZIhvcNAQELBQADgYEAtv2e3hBhsslXB1HTxgusjoschWOVtvGZUaYlhkKzKTCL\n"
+    "4YpDn50BccnucBU/b9phYvaEZtyzOv4ZXhxTGyLnLrIVB9x5ikfCcfl+LNYNjDwM\n"
+    "enm/h1zOfJ7wXLyscD4kU29Wc/zxBd70thIgLYn16CC1S9NtXKsXXDXv5VVH/bg=\n"
+    "-----END CERTIFICATE-----\n";
+
+// kCommonNameWithSANs is a leaf certificate signed by kSANTypesRoot, with
+// *.host1.test as the common name and no SAN list.
+static const char kCommonNameWithoutSANs[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIBtTCCAR6gAwIBAgIBAzANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp\n"
+    "bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y\n"
+    "MDk5MDEwMTAwMDAwMFowOjEhMB8GA1UEChMYQ29tbW9uIG5hbWUgd2l0aG91dCBT\n"
+    "QU5zMRUwEwYDVQQDDAwqLmhvc3QxLnRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMB\n"
+    "BwNCAARt2vjlIrPE+kr11VS1rRP/AYQu4fvf1bNw/K9rwYlVBhmLMPYasEmpCtKE\n"
+    "0bDIFydtDYC3wZDpSS+YiaG40sdAox8wHTAbBgNVHSMEFDASgBBAN9cB+0AvuBx+\n"
+    "VAQnjFkBMA0GCSqGSIb3DQEBCwUAA4GBAHRbIeaCEytOpJpw9O2dlB656AHe1+t5\n"
+    "4JiS5mvtzoVOLn7fFk5EFQtZS7sG1Uc2XjlSw+iyvFoTFEqfKyU/mIdc2vBuPwA2\n"
+    "+YXT8aE4S+UZ9oz5j0gDpikGnkSCW0cyHD8L8fntNjaQRSaM482JpmtdmuxClmWO\n"
+    "pFFXI2B5usgI\n"
+    "-----END CERTIFICATE-----\n";
+
+// kCommonNameWithEmailSAN is a leaf certificate signed by kSANTypesRoot, with
+// *.host1.test as the common name and the email address test@host2.test in the
+// SAN list.
+static const char kCommonNameWithEmailSAN[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIBvDCCASWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp\n"
+    "bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y\n"
+    "MDk5MDEwMTAwMDAwMFowFzEVMBMGA1UEAwwMKi5ob3N0MS50ZXN0MFkwEwYHKoZI\n"
+    "zj0CAQYIKoZIzj0DAQcDQgAEtevOxcTjpPzlNGoUMFfZyr1k03/Hiuh+EsnuScDs\n"
+    "8XLKi6fDkvSaDClI99ycabQZRPIrvyT+dglDC6ugQd+CYqNJMEcwDAYDVR0TAQH/\n"
+    "BAIwADAbBgNVHSMEFDASgBBAN9cB+0AvuBx+VAQnjFkBMBoGA1UdEQQTMBGBD3Rl\n"
+    "c3RAaG9zdDIudGVzdDANBgkqhkiG9w0BAQsFAAOBgQCGbqb78OWJWl4zb+qw0Dz2\n"
+    "HJgZZJt6/+nNG/XJKdaYeS4eofsbwsJI4fuuOF6ZvYCJxVNtGqdfZDgycvFA9hjv\n"
+    "NGosBF1/spP17cmzTahLjxs71jDvHV/EQJbKGl/Zpta1Em1VrzSrwoOFabPXzZTJ\n"
+    "aet/mER21Z/9ZsTUoJQPJw==\n"
+    "-----END CERTIFICATE-----\n";
+
+// kCommonNameWithIPSAN is a leaf certificate signed by kSANTypesRoot, with
+// *.host1.test as the common name and the IP address 127.0.0.1 in the
+// SAN list.
+static const char kCommonNameWithIPSAN[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIBsTCCARqgAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp\n"
+    "bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y\n"
+    "MDk5MDEwMTAwMDAwMFowFzEVMBMGA1UEAwwMKi5ob3N0MS50ZXN0MFkwEwYHKoZI\n"
+    "zj0CAQYIKoZIzj0DAQcDQgAEFKrgkxm8PysXbwnHQeTD3p8YY0+sY4ssnZgmj8wX\n"
+    "KTyn893fdBHWlz71GO6t82wMTF5d+ZYwI2XU52pfl4SB2aM+MDwwDAYDVR0TAQH/\n"
+    "BAIwADAbBgNVHSMEFDASgBBAN9cB+0AvuBx+VAQnjFkBMA8GA1UdEQQIMAaHBH8A\n"
+    "AAEwDQYJKoZIhvcNAQELBQADgYEAQWZ8Oj059ZjS109V/ijMYT28xuAN5n6HHxCO\n"
+    "DopTP56Zu9+gme5wTETWEfocspZvgecoUOcedTFoKSQ7JafO09NcVLA+D6ddYpju\n"
+    "mgfuiLy9dDhqvX/NHaLBMxOBWWbOLwWE+ibyX+pOzjWRCw1L7eUXOr6PhZAOQsmU\n"
+    "D0+O6KI=\n"
+    "-----END CERTIFICATE-----\n";
+
+// kConstrainedIntermediate is an intermediate signed by kSANTypesRoot, with
+// permitted DNS names of permitted1.test and foo.permitted2.test and an
+// excluded DNS name of excluded.permitted1.test. Its private key is:
+//
+// -----BEGIN PRIVATE KEY-----
+// MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTXUM4tJWM7OzATty
+// JhNOfIv/d8heWFBeKOfMR+RfaROhRANCAASbbbWYiN6mn+BCpg4XNpibOH0D/DN4
+// kZ5C/Ml2YVomC9T83OKk2CzB8fPAabPb4P4Vv+fIabpEfjWS5nzKLY1y
+// -----END PRIVATE KEY-----
+static const char kConstrainedIntermediate[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIICDjCCAXegAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp\n"
+    "bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y\n"
+    "MDk5MDEwMTAwMDAwMFowKDEmMCQGA1UEAxMdTmFtZSBDb25zdHJhaW50cyBJbnRl\n"
+    "cm1lZGlhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASbbbWYiN6mn+BCpg4X\n"
+    "NpibOH0D/DN4kZ5C/Ml2YVomC9T83OKk2CzB8fPAabPb4P4Vv+fIabpEfjWS5nzK\n"
+    "LY1yo4GJMIGGMA8GA1UdEwEB/wQFMAMBAf8wGwYDVR0jBBQwEoAQQDfXAftAL7gc\n"
+    "flQEJ4xZATBWBgNVHR4BAf8ETDBKoCowEYIPcGVybWl0dGVkMS50ZXN0MBWCE2Zv\n"
+    "by5wZXJtaXR0ZWQyLnRlc3ShHDAaghhleGNsdWRlZC5wZXJtaXR0ZWQxLnRlc3Qw\n"
+    "DQYJKoZIhvcNAQELBQADgYEAFq1Ka05hiKREwRpSceQPzIIH4B5a5IVBg5/EvmQI\n"
+    "9V0fXyAE1GmahPt70sIBxIgzNTEaY8P/IoOuCdlZWe0msmyEO3S6YSAzOWR5Van6\n"
+    "cXmFM1uMd95TlkxUMRdV+jKJTvG6R/BM2zltaV7Xt662k5HtzT5Svw0rZlFaggZz\n"
+    "UyM=\n"
+    "-----END CERTIFICATE-----\n";
+
+// kCommonNamePermittedLeaf is a leaf certificate signed by
+// kConstrainedIntermediate. Its common name is permitted by the name
+// constraints.
+static const char kCommonNamePermittedLeaf[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIBaDCCAQ2gAwIBAgIBAzAKBggqhkjOPQQDAjAoMSYwJAYDVQQDEx1OYW1lIENv\n"
+    "bnN0cmFpbnRzIEludGVybWVkaWF0ZTAgFw0wMDAxMDEwMDAwMDBaGA8yMDk5MDEw\n"
+    "MTAwMDAwMFowPjEeMBwGA1UEChMVQ29tbW9uIG5hbWUgcGVybWl0dGVkMRwwGgYD\n"
+    "VQQDExNmb28ucGVybWl0dGVkMS50ZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD\n"
+    "QgAENX5Ycs8q8MRzPYUz6DqLHhJR3wcmniFRgkiEa7MxE/mRe00y0VGwH7xi7Aoc\n"
+    "emXPrtD4JwN5bssbcxWGAKYYzaMQMA4wDAYDVR0TAQH/BAIwADAKBggqhkjOPQQD\n"
+    "AgNJADBGAiEAtsnWuRQXtw2xbieC78Y8SVEtTjcZUx8uZyQe1GPLfGICIQDR4fNY\n"
+    "yg3PC94ydPNQZVsFxAne32CbonWWsokalTFpUQ==\n"
+    "-----END CERTIFICATE-----\n";
+static const char kCommonNamePermitted[] = "foo.permitted1.test";
+
+// kCommonNameNotPermittedLeaf is a leaf certificate signed by
+// kConstrainedIntermediate. Its common name is not permitted by the name
+// constraints.
+static const char kCommonNameNotPermittedLeaf[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIBazCCARCgAwIBAgIBBDAKBggqhkjOPQQDAjAoMSYwJAYDVQQDEx1OYW1lIENv\n"
+    "bnN0cmFpbnRzIEludGVybWVkaWF0ZTAgFw0wMDAxMDEwMDAwMDBaGA8yMDk5MDEw\n"
+    "MTAwMDAwMFowQTEiMCAGA1UEChMZQ29tbW9uIG5hbWUgbm90IHBlcm1pdHRlZDEb\n"
+    "MBkGA1UEAxMSbm90LXBlcm1pdHRlZC50ZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0D\n"
+    "AQcDQgAEzfghKuWf0JoXb0Drp09C3yXMSQQ1byt+AUaymvsHOWsxQ9v1Q+vkF/IM\n"
+    "HRqGTk2TyxrB2iClVEn/Uu+YtYox1KMQMA4wDAYDVR0TAQH/BAIwADAKBggqhkjO\n"
+    "PQQDAgNJADBGAiEAxaUslxmoWL1tIvnDz7gDkto/HcmdU0jHVuUQLXcCG8wCIQCN\n"
+    "5xZjitlCQU8UB5qSu9wH4B+0JcVO3Ss4Az76HEJWMw==\n"
+    "-----END CERTIFICATE-----\n";
+static const char kCommonNameNotPermitted[] = "not-permitted.test";
+
+// kCommonNameNotPermittedWithSANsLeaf is a leaf certificate signed by
+// kConstrainedIntermediate. Its common name is not permitted by the name
+// constraints but it has a SAN list.
+static const char kCommonNameNotPermittedWithSANsLeaf[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIBqTCCAU+gAwIBAgIBBjAKBggqhkjOPQQDAjAoMSYwJAYDVQQDEx1OYW1lIENv\n"
+    "bnN0cmFpbnRzIEludGVybWVkaWF0ZTAgFw0wMDAxMDEwMDAwMDBaGA8yMDk5MDEw\n"
+    "MTAwMDAwMFowSzEsMCoGA1UEChMjQ29tbW9uIG5hbWUgbm90IHBlcm1pdHRlZCB3\n"
+    "aXRoIFNBTlMxGzAZBgNVBAMTEm5vdC1wZXJtaXR0ZWQudGVzdDBZMBMGByqGSM49\n"
+    "AgEGCCqGSM49AwEHA0IABKsn9wOApXFHrqhLdQgbFSeaSoAIbxgO0zVSRZUb5naR\n"
+    "93zoL3MFOvZEF8xiEqh7le+l3XuUig0fwqpcsZzRNJajRTBDMAwGA1UdEwEB/wQC\n"
+    "MAAwMwYDVR0RBCwwKoITZm9vLnBlcm1pdHRlZDEudGVzdIITZm9vLnBlcm1pdHRl\n"
+    "ZDIudGVzdDAKBggqhkjOPQQDAgNIADBFAiACk+1f184KkKAXuntmrz+Ygcq8MiZl\n"
+    "4delx44FtcNaegIhAIA5nYfzxNcTXxDo3U+x1vSLH6Y7faLvHiFySp7O//q+\n"
+    "-----END CERTIFICATE-----\n";
+static const char kCommonNameNotPermittedWithSANs[] = "not-permitted.test";
+
+// kCommonNameNotDNSLeaf is a leaf certificate signed by
+// kConstrainedIntermediate. Its common name is not a DNS name.
+static const char kCommonNameNotDNSLeaf[] =
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIBYTCCAQagAwIBAgIBCDAKBggqhkjOPQQDAjAoMSYwJAYDVQQDEx1OYW1lIENv\n"
+    "bnN0cmFpbnRzIEludGVybWVkaWF0ZTAgFw0wMDAxMDEwMDAwMDBaGA8yMDk5MDEw\n"
+    "MTAwMDAwMFowNzEcMBoGA1UEChMTQ29tbW9uIG5hbWUgbm90IEROUzEXMBUGA1UE\n"
+    "AxMOTm90IGEgRE5TIG5hbWUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASnueyc\n"
+    "Zxtnw5ke2J2T0/LwAK37auQP/RSFd9mem+BJVbgviawtAlignJmafp7Zw4/GdYEJ\n"
+    "Vm8qlriOJtluvXGcoxAwDjAMBgNVHRMBAf8EAjAAMAoGCCqGSM49BAMCA0kAMEYC\n"
+    "IQChUAmVNI39VHe0zemRE09VDcSEgOxr1nTvjLcg/Q8pVQIhAJYZnJI0YZAi05QH\n"
+    "RHNlAkTK2TnUaVn3fGSylaLiFS1r\n"
+    "-----END CERTIFICATE-----\n";
+static const char kCommonNameNotDNS[] = "Not a DNS name";
+
 // CertFromPEM parses the given, NUL-terminated pem block and returns an
 // |X509*|.
 static bssl::UniquePtr<X509> CertFromPEM(const char *pem) {
@@ -723,7 +880,8 @@
                   const std::vector<X509 *> &intermediates,
                   const std::vector<X509_CRL *> &crls, unsigned long flags,
                   bool use_additional_untrusted,
-                  std::function<void(X509_VERIFY_PARAM *)> configure_callback) {
+                  std::function<void(X509_VERIFY_PARAM *)> configure_callback,
+                  int (*verify_callback)(int, X509_STORE_CTX *) = nullptr) {
   bssl::UniquePtr<STACK_OF(X509)> roots_stack(CertsToStack(roots));
   bssl::UniquePtr<STACK_OF(X509)> intermediates_stack(
       CertsToStack(intermediates));
@@ -1169,9 +1327,11 @@
   uint8_t pub_bytes[32], priv_bytes[64];
   ED25519_keypair(pub_bytes, priv_bytes);
 
-  bssl::UniquePtr<EVP_PKEY> pub(EVP_PKEY_new_ed25519_public(pub_bytes));
+  bssl::UniquePtr<EVP_PKEY> pub(
+      EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr, pub_bytes, 32));
   ASSERT_TRUE(pub);
-  bssl::UniquePtr<EVP_PKEY> priv(EVP_PKEY_new_ed25519_private(priv_bytes));
+  bssl::UniquePtr<EVP_PKEY> priv(
+      EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, priv_bytes, 32));
   ASSERT_TRUE(priv);
 
   bssl::ScopedEVP_MD_CTX md_ctx;
@@ -1744,3 +1904,172 @@
   ASSERT_TRUE(cert2);
   EXPECT_EQ(0, X509_cmp(cert.get(), cert2.get()));
 }
+
+TEST(X509Test, CommonNameFallback) {
+  bssl::UniquePtr<X509> root = CertFromPEM(kSANTypesRoot);
+  ASSERT_TRUE(root);
+  bssl::UniquePtr<X509> with_sans = CertFromPEM(kCommonNameWithSANs);
+  ASSERT_TRUE(with_sans);
+  bssl::UniquePtr<X509> without_sans = CertFromPEM(kCommonNameWithoutSANs);
+  ASSERT_TRUE(without_sans);
+  bssl::UniquePtr<X509> with_email = CertFromPEM(kCommonNameWithEmailSAN);
+  ASSERT_TRUE(with_email);
+  bssl::UniquePtr<X509> with_ip = CertFromPEM(kCommonNameWithIPSAN);
+  ASSERT_TRUE(with_ip);
+
+  auto verify_cert = [&](X509 *leaf, unsigned flags, const char *host) {
+    return Verify(
+        leaf, {root.get()}, {}, {}, 0, false, [&](X509_VERIFY_PARAM *param) {
+          ASSERT_TRUE(X509_VERIFY_PARAM_set1_host(param, host, strlen(host)));
+          X509_VERIFY_PARAM_set_hostflags(param, flags);
+        });
+  };
+
+  // By default, the common name is ignored if the SAN list is present but
+  // otherwise is checked.
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(with_sans.get(), 0 /* no flags */, "foo.host1.test"));
+  EXPECT_EQ(X509_V_OK,
+            verify_cert(with_sans.get(), 0 /* no flags */, "foo.host2.test"));
+  EXPECT_EQ(X509_V_OK,
+            verify_cert(with_sans.get(), 0 /* no flags */, "foo.host3.test"));
+  EXPECT_EQ(X509_V_OK, verify_cert(without_sans.get(), 0 /* no flags */,
+                                   "foo.host1.test"));
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(with_email.get(), 0 /* no flags */, "foo.host1.test"));
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(with_ip.get(), 0 /* no flags */, "foo.host1.test"));
+
+  // X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT is ignored.
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(with_sans.get(), X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
+                        "foo.host1.test"));
+  EXPECT_EQ(X509_V_OK,
+            verify_cert(with_sans.get(), X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
+                        "foo.host2.test"));
+  EXPECT_EQ(X509_V_OK,
+            verify_cert(with_sans.get(), X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
+                        "foo.host3.test"));
+  EXPECT_EQ(X509_V_OK, verify_cert(without_sans.get(),
+                                   X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
+                                   "foo.host1.test"));
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(with_email.get(), X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
+                        "foo.host1.test"));
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(with_ip.get(), X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
+                        "foo.host1.test"));
+
+  // X509_CHECK_FLAG_NEVER_CHECK_SUBJECT implements the correct behavior: the
+  // common name is never checked.
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(with_sans.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
+                        "foo.host1.test"));
+  EXPECT_EQ(X509_V_OK,
+            verify_cert(with_sans.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
+                        "foo.host2.test"));
+  EXPECT_EQ(X509_V_OK,
+            verify_cert(with_sans.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
+                        "foo.host3.test"));
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(without_sans.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
+                        "foo.host1.test"));
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(with_email.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
+                        "foo.host1.test"));
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(with_ip.get(), X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
+                        "foo.host1.test"));
+}
+
+TEST(X509Test, LooksLikeDNSName) {
+    static const char *kValid[] = {
+        "example.com",
+        "eXample123-.com",
+        "*.example.com",
+        "exa_mple.com",
+        "example.com.",
+        "project-dev:us-central1:main",
+    };
+    static const char *kInvalid[] = {
+        "-eXample123-.com",
+        "",
+        ".",
+        "*",
+        "*.",
+        "example..com",
+        ".example.com",
+        "example.com..",
+        "*foo.example.com",
+        "foo.*.example.com",
+        "foo,bar",
+    };
+
+    for (const char *str : kValid) {
+      SCOPED_TRACE(str);
+      EXPECT_TRUE(x509v3_looks_like_dns_name(
+          reinterpret_cast<const uint8_t *>(str), strlen(str)));
+    }
+    for (const char *str : kInvalid) {
+      SCOPED_TRACE(str);
+      EXPECT_FALSE(x509v3_looks_like_dns_name(
+          reinterpret_cast<const uint8_t *>(str), strlen(str)));
+    }
+}
+
+TEST(X509Test, CommonNameAndNameConstraints) {
+  bssl::UniquePtr<X509> root = CertFromPEM(kSANTypesRoot);
+  ASSERT_TRUE(root);
+  bssl::UniquePtr<X509> intermediate = CertFromPEM(kConstrainedIntermediate);
+  ASSERT_TRUE(intermediate);
+  bssl::UniquePtr<X509> permitted = CertFromPEM(kCommonNamePermittedLeaf);
+  ASSERT_TRUE(permitted);
+  bssl::UniquePtr<X509> not_permitted =
+      CertFromPEM(kCommonNameNotPermittedLeaf);
+  ASSERT_TRUE(not_permitted);
+  bssl::UniquePtr<X509> not_permitted_with_sans =
+      CertFromPEM(kCommonNameNotPermittedWithSANsLeaf);
+  ASSERT_TRUE(not_permitted_with_sans);
+  bssl::UniquePtr<X509> not_dns = CertFromPEM(kCommonNameNotDNSLeaf);
+  ASSERT_TRUE(not_dns);
+
+  auto verify_cert = [&](X509 *leaf, unsigned flags, const char *host) {
+    return Verify(
+        leaf, {root.get()}, {intermediate.get()}, {}, 0, false,
+        [&](X509_VERIFY_PARAM *param) {
+          ASSERT_TRUE(X509_VERIFY_PARAM_set1_host(param, host, strlen(host)));
+          X509_VERIFY_PARAM_set_hostflags(param, flags);
+        });
+  };
+
+  // Certificates which would otherwise trigger the common name fallback are
+  // rejected whenever there are name constraints. We do this whether or not
+  // the common name matches the constraints.
+  EXPECT_EQ(
+      X509_V_ERR_NAME_CONSTRAINTS_WITHOUT_SANS,
+      verify_cert(permitted.get(), 0 /* no flags */, kCommonNamePermitted));
+  EXPECT_EQ(X509_V_ERR_NAME_CONSTRAINTS_WITHOUT_SANS,
+            verify_cert(not_permitted.get(), 0 /* no flags */,
+                        kCommonNameNotPermitted));
+
+  // This occurs even if the built-in name checks aren't used. The caller may
+  // separately call |X509_check_host|.
+  EXPECT_EQ(X509_V_ERR_NAME_CONSTRAINTS_WITHOUT_SANS,
+            Verify(not_permitted.get(), {root.get()}, {intermediate.get()}, {},
+                   0 /* no flags */, false, nullptr));
+
+  // If the leaf certificate has SANs, the common name fallback is always
+  // disabled, so the name constraints do not apply.
+  EXPECT_EQ(X509_V_OK, Verify(not_permitted_with_sans.get(), {root.get()},
+                              {intermediate.get()}, {}, 0, false, nullptr));
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(not_permitted_with_sans.get(), 0 /* no flags */,
+                        kCommonNameNotPermittedWithSANs));
+
+  // If the common name does not look like a DNS name, we apply neither name
+  // constraints nor common name fallback.
+  EXPECT_EQ(X509_V_OK, Verify(not_dns.get(), {root.get()}, {intermediate.get()},
+                              {}, 0, false, nullptr));
+  EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+            verify_cert(not_dns.get(), 0 /* no flags */, kCommonNameNotDNS));
+}
diff --git a/src/crypto/x509/x509_txt.c b/src/crypto/x509/x509_txt.c
index 753e720..8e6ac27 100644
--- a/src/crypto/x509/x509_txt.c
+++ b/src/crypto/x509/x509_txt.c
@@ -54,13 +54,10 @@
  * copied and put under another distribution licence
  * [including the GNU Public Licence.] */
 
-#include <openssl/mem.h>
 #include <openssl/x509.h>
 
 const char *X509_verify_cert_error_string(long n)
 {
-    static char buf[100];
-
     switch ((int)n) {
     case X509_V_OK:
         return ("ok");
@@ -198,8 +195,10 @@
     case X509_V_ERR_STORE_LOOKUP:
         return ("Issuer certificate lookup error");
 
+    case X509_V_ERR_NAME_CONSTRAINTS_WITHOUT_SANS:
+        return "Issuer has name constraints but leaf has no SANs";
+
     default:
-        BIO_snprintf(buf, sizeof buf, "error number %ld", n);
-        return (buf);
+        return "unknown certificate verification error";
     }
 }
diff --git a/src/crypto/x509/x509_vfy.c b/src/crypto/x509/x509_vfy.c
index 5af3fb3..fff97fa 100644
--- a/src/crypto/x509/x509_vfy.c
+++ b/src/crypto/x509/x509_vfy.c
@@ -70,6 +70,7 @@
 
 #include "vpm_int.h"
 #include "../internal.h"
+#include "../x509v3/internal.h"
 
 static CRYPTO_EX_DATA_CLASS g_ex_data_class =
     CRYPTO_EX_DATA_CLASS_INIT_WITH_APP_DATA;
@@ -710,13 +711,40 @@
     return ok;
 }
 
+static int reject_dns_name_in_common_name(X509 *x509)
+{
+    X509_NAME *name = X509_get_subject_name(x509);
+    int i = -1;
+    for (;;) {
+        i = X509_NAME_get_index_by_NID(name, NID_commonName, i);
+        if (i == -1) {
+            return X509_V_OK;
+        }
+
+        X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, i);
+        ASN1_STRING *common_name = X509_NAME_ENTRY_get_data(entry);
+        unsigned char *idval;
+        int idlen = ASN1_STRING_to_UTF8(&idval, common_name);
+        if (idlen < 0) {
+            return X509_V_ERR_OUT_OF_MEM;
+        }
+        /* Only process attributes that look like host names. Note it is
+         * important that this check be mirrored in |X509_check_host|. */
+        int looks_like_dns = x509v3_looks_like_dns_name(idval, (size_t)idlen);
+        OPENSSL_free(idval);
+        if (looks_like_dns) {
+            return X509_V_ERR_NAME_CONSTRAINTS_WITHOUT_SANS;
+        }
+    }
+}
+
 static int check_name_constraints(X509_STORE_CTX *ctx)
 {
-    X509 *x;
     int i, j, rv;
+    int has_name_constraints = 0;
     /* Check name constraints for all certificates */
     for (i = sk_X509_num(ctx->chain) - 1; i >= 0; i--) {
-        x = sk_X509_value(ctx->chain, i);
+        X509 *x = sk_X509_value(ctx->chain, i);
         /* Ignore self issued certs unless last in chain */
         if (i && (x->ex_flags & EXFLAG_SI))
             continue;
@@ -729,6 +757,7 @@
         for (j = sk_X509_num(ctx->chain) - 1; j > i; j--) {
             NAME_CONSTRAINTS *nc = sk_X509_value(ctx->chain, j)->nc;
             if (nc) {
+                has_name_constraints = 1;
                 rv = NAME_CONSTRAINTS_check(x, nc);
                 switch (rv) {
                 case X509_V_OK:
@@ -747,6 +776,36 @@
             }
         }
     }
+
+    /* Name constraints do not match against the common name, but
+     * |X509_check_host| still implements the legacy behavior where, on
+     * certificates lacking a SAN list, DNS-like names in the common name are
+     * checked instead.
+     *
+     * While we could apply the name constraints to the common name, name
+     * constraints are rare enough that can hold such certificates to a higher
+     * standard. Note this does not make "DNS-like" heuristic failures any
+     * worse. A decorative common-name misidentified as a DNS name would fail
+     * the name constraint anyway. */
+    X509 *leaf = sk_X509_value(ctx->chain, 0);
+    if (has_name_constraints && leaf->altname == NULL) {
+        rv = reject_dns_name_in_common_name(leaf);
+        switch (rv) {
+        case X509_V_OK:
+            break;
+        case X509_V_ERR_OUT_OF_MEM:
+            ctx->error = rv;
+            return 0;
+        default:
+            ctx->error = rv;
+            ctx->error_depth = i;
+            ctx->current_cert = leaf;
+            if (!ctx->verify_cb(0, ctx))
+                return 0;
+            break;
+        }
+    }
+
     return 1;
 }
 
diff --git a/src/crypto/x509v3/internal.h b/src/crypto/x509v3/internal.h
index e6be684..c143d73 100644
--- a/src/crypto/x509v3/internal.h
+++ b/src/crypto/x509v3/internal.h
@@ -43,6 +43,11 @@
 // followed by '.'. Otherwise, it returns a non-zero number.
 int x509v3_name_cmp(const char *name, const char *cmp);
 
+// x509v3_looks_like_dns_name returns one if |in| looks like a DNS name and zero
+// otherwise.
+OPENSSL_EXPORT int x509v3_looks_like_dns_name(const unsigned char *in,
+                                              size_t len);
+
 
 #if defined(__cplusplus)
 }  /* extern C */
diff --git a/src/crypto/x509v3/v3_utl.c b/src/crypto/x509v3/v3_utl.c
index 2a293dc..86c4940 100644
--- a/src/crypto/x509v3/v3_utl.c
+++ b/src/crypto/x509v3/v3_utl.c
@@ -909,6 +909,53 @@
                           subject, subject_len, flags);
 }
 
+int x509v3_looks_like_dns_name(const unsigned char *in, size_t len) {
+    /* This function is used as a heuristic for whether a common name is a
+     * hostname to be matched, or merely a decorative name to describe the
+     * subject. This heuristic must be applied to both name constraints and the
+     * common name fallback, so it must be loose enough to accept hostname
+     * common names, and tight enough to reject decorative common names. */
+
+    if (len > 0 && in[len - 1] == '.') {
+        len--;
+    }
+
+    /* Wildcards are allowed in front. */
+    if (len >= 2 && in[0] == '*' && in[1] == '.') {
+        in += 2;
+        len -= 2;
+    }
+
+    if (len == 0) {
+        return 0;
+    }
+
+    size_t label_start = 0;
+    for (size_t i = 0; i < len; i++) {
+        unsigned char c = in[i];
+        if ((c >= 'a' && c <= 'z') ||
+            (c >= '0' && c <= '9') ||
+            (c >= 'A' && c <= 'Z') ||
+            (c == '-' && i > label_start) ||
+            /* These are not valid characters in hostnames, but commonly found
+             * in deployments outside the Web PKI. */
+            c == '_' ||
+            c == ':') {
+            continue;
+        }
+
+        /* Labels must not be empty. */
+        if (c == '.' && i > label_start && i < len - 1) {
+            label_start = i + 1;
+            continue;
+        }
+
+        return 0;
+    }
+
+    return 1;
+}
+
 /*
  * Compare an ASN1_STRING to a supplied string. If they match return 1. If
  * cmp_type > 0 only compare if string matches the type, otherwise convert it
@@ -916,8 +963,8 @@
  */
 
 static int do_check_string(ASN1_STRING *a, int cmp_type, equal_fn equal,
-                           unsigned int flags, const char *b, size_t blen,
-                           char **peername)
+                           unsigned int flags, int check_type, const char *b,
+                           size_t blen, char **peername)
 {
     int rv = 0;
 
@@ -938,7 +985,17 @@
         astrlen = ASN1_STRING_to_UTF8(&astr, a);
         if (astrlen < 0)
             return -1;
-        rv = equal(astr, astrlen, (unsigned char *)b, blen, flags);
+        /*
+         * We check the common name against DNS name constraints if it passes
+         * |x509v3_looks_like_dns_name|. Thus we must not consider common names
+         * for DNS fallbacks if they fail this check.
+         */
+        if (check_type == GEN_DNS &&
+            !x509v3_looks_like_dns_name(astr, astrlen)) {
+            rv = 0;
+        } else {
+            rv = equal(astr, astrlen, (unsigned char *)b, blen, flags);
+        }
         if (rv > 0 && peername)
             *peername = BUF_strndup((char *)astr, astrlen);
         OPENSSL_free(astr);
@@ -955,7 +1012,6 @@
     int j;
     int cnid = NID_undef;
     int alt_type;
-    int san_present = 0;
     int rv = 0;
     equal_fn equal;
 
@@ -988,7 +1044,6 @@
             gen = sk_GENERAL_NAME_value(gens, i);
             if (gen->type != check_type)
                 continue;
-            san_present = 1;
             if (check_type == GEN_EMAIL)
                 cstr = gen->d.rfc822Name;
             else if (check_type == GEN_DNS)
@@ -996,21 +1051,16 @@
             else
                 cstr = gen->d.iPAddress;
             /* Positive on success, negative on error! */
-            if ((rv = do_check_string(cstr, alt_type, equal, flags,
+            if ((rv = do_check_string(cstr, alt_type, equal, flags, check_type,
                                       chk, chklen, peername)) != 0)
                 break;
         }
         GENERAL_NAMES_free(gens);
-        if (rv != 0)
-            return rv;
-        if (cnid == NID_undef
-            || (san_present
-                && !(flags & X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT)))
-            return 0;
+        return rv;
     }
 
     /* We're done if CN-ID is not pertinent */
-    if (cnid == NID_undef)
+    if (cnid == NID_undef || (flags & X509_CHECK_FLAG_NEVER_CHECK_SUBJECT))
         return 0;
 
     j = -1;
@@ -1021,7 +1071,7 @@
         ne = X509_NAME_get_entry(name, j);
         str = X509_NAME_ENTRY_get_data(ne);
         /* Positive on success, negative on error! */
-        if ((rv = do_check_string(str, -1, equal, flags,
+        if ((rv = do_check_string(str, -1, equal, flags, check_type,
                                   chk, chklen, peername)) != 0)
             return rv;
     }
diff --git a/src/crypto/x509v3/v3name_test.cc b/src/crypto/x509v3/v3name_test.cc
index 0736120..2dcdd87 100644
--- a/src/crypto/x509v3/v3name_test.cc
+++ b/src/crypto/x509v3/v3name_test.cc
@@ -65,6 +65,7 @@
 #include <openssl/x509v3.h>
 
 #include "../internal.h"
+#include "internal.h"
 
 
 static const char *const names[] = {
@@ -344,7 +345,7 @@
         ret = X509_check_host(crt, name, namelen, 0, NULL);
         match = -1;
         if (ret < 0) {
-            fprintf(stderr, "internal error in X509_check_host");
+            fprintf(stderr, "internal error in X509_check_host\n");
             ++errors;
         } else if (fn->host) {
             if (ret == 1 && !samename)
@@ -359,7 +360,7 @@
                               X509_CHECK_FLAG_NO_WILDCARDS, NULL);
         match = -1;
         if (ret < 0) {
-            fprintf(stderr, "internal error in X509_check_host");
+            fprintf(stderr, "internal error in X509_check_host\n");
             ++errors;
         } else if (fn->host) {
             if (ret == 1 && !samename)
@@ -385,12 +386,21 @@
     }
 }
 
-// TOOD(davidben): Convert this test to GTest more thoroughly.
+// TODO(davidben): Convert this test to GTest more thoroughly.
 TEST(X509V3Test, NameTest) {
     const struct set_name_fn *pfn = name_fns;
     while (pfn->name) {
         const char *const *pname = names;
         while (*pname) {
+            // The common name fallback requires the name look sufficiently
+            // DNS-like.
+            if (strcmp(pfn->name, "set CN") == 0 &&
+                !x509v3_looks_like_dns_name(
+                    reinterpret_cast<const unsigned char*>(*pname),
+                    strlen(*pname))) {
+                ++pname;
+                continue;
+            }
             bssl::UniquePtr<X509> crt(make_cert());
             ASSERT_TRUE(crt);
             ASSERT_TRUE(pfn->fn(crt.get(), *pname));