external/boringssl: Sync to 7f7e5e231efec6e86d6c7d3fd1b759be1cece156.

This includes the following changes:

https://boringssl.googlesource.com/boringssl/+log/dc332205326340ec1bf7d553452c7699904054b4..7f7e5e231efec6e86d6c7d3fd1b759be1cece156

Test: BoringSSL CTS Presubmits.
Change-Id: I0eaa045ec1cf8ac076814bab47a2658f8a5ee0e9
diff --git a/BORINGSSL_REVISION b/BORINGSSL_REVISION
index 6b53a5c..9724cc9 100644
--- a/BORINGSSL_REVISION
+++ b/BORINGSSL_REVISION
@@ -1 +1 @@
-dc332205326340ec1bf7d553452c7699904054b4
+7f7e5e231efec6e86d6c7d3fd1b759be1cece156
diff --git a/err_data.c b/err_data.c
index 8c0f0ca..e295820 100644
--- a/err_data.c
+++ b/err_data.c
@@ -187,44 +187,44 @@
     0x28340c60,
     0x283480ac,
     0x283500ea,
-    0x2c322e5e,
+    0x2c322e92,
     0x2c3292bd,
-    0x2c332e6c,
-    0x2c33ae7e,
-    0x2c342e92,
-    0x2c34aea4,
-    0x2c352ebf,
-    0x2c35aed1,
-    0x2c362ee4,
+    0x2c332ea0,
+    0x2c33aeb2,
+    0x2c342ec6,
+    0x2c34aed8,
+    0x2c352ef3,
+    0x2c35af05,
+    0x2c362f18,
     0x2c36832d,
-    0x2c372ef1,
-    0x2c37af03,
-    0x2c382f28,
-    0x2c38af3f,
-    0x2c392f4d,
-    0x2c39af5d,
-    0x2c3a2f6f,
-    0x2c3aaf83,
-    0x2c3b2f94,
-    0x2c3bafb3,
+    0x2c372f25,
+    0x2c37af37,
+    0x2c382f5c,
+    0x2c38af73,
+    0x2c392f81,
+    0x2c39af91,
+    0x2c3a2fa3,
+    0x2c3aafb7,
+    0x2c3b2fc8,
+    0x2c3bafe7,
     0x2c3c12cf,
     0x2c3c92e5,
-    0x2c3d2fc7,
+    0x2c3d2ffb,
     0x2c3d92fe,
-    0x2c3e2fe4,
-    0x2c3eaff2,
-    0x2c3f300a,
-    0x2c3fb022,
-    0x2c40304c,
+    0x2c3e3018,
+    0x2c3eb026,
+    0x2c3f303e,
+    0x2c3fb056,
+    0x2c403080,
     0x2c4091d0,
-    0x2c41305d,
-    0x2c41b070,
+    0x2c413091,
+    0x2c41b0a4,
     0x2c421196,
-    0x2c42b081,
+    0x2c42b0b5,
     0x2c430722,
-    0x2c43afa5,
-    0x2c442f16,
-    0x2c44b02f,
+    0x2c43afd9,
+    0x2c442f4a,
+    0x2c44b063,
     0x30320000,
     0x30328015,
     0x3033001f,
@@ -456,61 +456,61 @@
     0x4061a3c9,
     0x406223e0,
     0x4062a3f1,
-    0x40632402,
-    0x4063a417,
-    0x4064242e,
-    0x4064a45a,
-    0x40652475,
-    0x4065a48c,
-    0x406624a4,
-    0x4066a4ce,
-    0x406724f9,
-    0x4067a53e,
-    0x40682586,
-    0x4068a5a7,
-    0x406925d9,
-    0x4069a607,
-    0x406a2628,
-    0x406aa648,
-    0x406b27d0,
-    0x406ba7f3,
-    0x406c2809,
-    0x406caaac,
-    0x406d2adb,
-    0x406dab03,
-    0x406e2b31,
-    0x406eab7e,
-    0x406f2bb9,
-    0x406fabf1,
-    0x40702c04,
-    0x4070ac21,
+    0x40632416,
+    0x4063a42b,
+    0x40642442,
+    0x4064a46e,
+    0x40652489,
+    0x4065a4a0,
+    0x406624b8,
+    0x4066a4e2,
+    0x4067250d,
+    0x4067a552,
+    0x4068259a,
+    0x4068a5bb,
+    0x406925ed,
+    0x4069a61b,
+    0x406a263c,
+    0x406aa65c,
+    0x406b27e4,
+    0x406ba807,
+    0x406c281d,
+    0x406caac0,
+    0x406d2aef,
+    0x406dab17,
+    0x406e2b45,
+    0x406eab92,
+    0x406f2bcd,
+    0x406fac05,
+    0x40702c18,
+    0x4070ac35,
     0x40710802,
-    0x4071ac33,
-    0x40722c46,
-    0x4072ac7c,
-    0x40732c94,
+    0x4071ac47,
+    0x40722c5a,
+    0x4072ac90,
+    0x40732ca8,
     0x407394cf,
-    0x40742ca8,
-    0x4074acc2,
-    0x40752cd3,
-    0x4075ace7,
-    0x40762cf5,
+    0x40742cbc,
+    0x4074acd6,
+    0x40752ce7,
+    0x4075acfb,
+    0x40762d09,
     0x40769293,
-    0x40772d1a,
-    0x4077ad3c,
-    0x40782d57,
-    0x4078ad90,
-    0x40792da7,
-    0x4079adbd,
-    0x407a2dc9,
-    0x407aaddc,
-    0x407b2df1,
-    0x407bae03,
-    0x407c2e34,
-    0x407cae3d,
-    0x407d25c2,
+    0x40772d2e,
+    0x4077ad50,
+    0x40782d6b,
+    0x4078ada4,
+    0x40792dbb,
+    0x4079add1,
+    0x407a2dfd,
+    0x407aae10,
+    0x407b2e25,
+    0x407bae37,
+    0x407c2e68,
+    0x407cae71,
+    0x407d25d6,
     0x407d9f82,
-    0x407e2d6c,
+    0x407e2d80,
     0x407ea177,
     0x407f1d2d,
     0x407f9ad3,
@@ -518,77 +518,79 @@
     0x40809d55,
     0x4081201a,
     0x40819f33,
-    0x40822b1c,
+    0x40822b30,
     0x40829ab9,
     0x40832152,
-    0x4083a43f,
+    0x4083a453,
     0x40841d69,
     0x4084a1af,
     0x40852234,
     0x4085a358,
     0x408622b4,
     0x40869f9c,
-    0x40872b62,
+    0x40872b76,
     0x4087a3a6,
     0x40881b1a,
-    0x4088a551,
+    0x4088a565,
     0x40891b69,
     0x40899af6,
-    0x408a2841,
+    0x408a2855,
     0x408a98e7,
-    0x408b2e18,
-    0x408babce,
+    0x408b2e4c,
+    0x408babe2,
     0x408c2244,
     0x408c9903,
     0x408d1dca,
     0x408d9d9b,
     0x408e1ee4,
     0x408ea0ba,
-    0x408f2565,
+    0x408f2579,
     0x408fa374,
-    0x4090251a,
+    0x4090252e,
     0x4090a286,
-    0x40912829,
+    0x4091283d,
     0x40919929,
     0x40921bb6,
-    0x4092ab9d,
-    0x40932c5f,
+    0x4092abb1,
+    0x40932c73,
     0x40939fad,
     0x40941d7d,
-    0x4094a85a,
-    0x41f426fb,
-    0x41f9278d,
-    0x41fe2680,
-    0x41fea89d,
-    0x41ff298e,
-    0x42032714,
-    0x42082736,
-    0x4208a772,
-    0x42092664,
-    0x4209a7ac,
-    0x420a26bb,
-    0x420aa69b,
-    0x420b26db,
-    0x420ba754,
-    0x420c29aa,
-    0x420ca86a,
-    0x420d2884,
-    0x420da8bb,
-    0x421228d5,
-    0x42172971,
-    0x4217a917,
-    0x421c2939,
-    0x421f28f4,
-    0x422129c1,
-    0x42262954,
-    0x422b2a90,
-    0x422baa3e,
-    0x422c2a78,
-    0x422ca9fd,
-    0x422d29dc,
-    0x422daa5d,
-    0x422e2a23,
-    0x422eab49,
+    0x4094a86e,
+    0x40952402,
+    0x4095addd,
+    0x41f4270f,
+    0x41f927a1,
+    0x41fe2694,
+    0x41fea8b1,
+    0x41ff29a2,
+    0x42032728,
+    0x4208274a,
+    0x4208a786,
+    0x42092678,
+    0x4209a7c0,
+    0x420a26cf,
+    0x420aa6af,
+    0x420b26ef,
+    0x420ba768,
+    0x420c29be,
+    0x420ca87e,
+    0x420d2898,
+    0x420da8cf,
+    0x421228e9,
+    0x42172985,
+    0x4217a92b,
+    0x421c294d,
+    0x421f2908,
+    0x422129d5,
+    0x42262968,
+    0x422b2aa4,
+    0x422baa52,
+    0x422c2a8c,
+    0x422caa11,
+    0x422d29f0,
+    0x422daa71,
+    0x422e2a37,
+    0x422eab5d,
     0x4432072d,
     0x4432873c,
     0x44330748,
@@ -643,69 +645,69 @@
     0x4c41152c,
     0x4c4193af,
     0x4c421518,
-    0x50323093,
-    0x5032b0a2,
-    0x503330ad,
-    0x5033b0bd,
-    0x503430d6,
-    0x5034b0f0,
-    0x503530fe,
-    0x5035b114,
-    0x50363126,
-    0x5036b13c,
-    0x50373155,
-    0x5037b168,
-    0x50383180,
-    0x5038b191,
-    0x503931a6,
-    0x5039b1ba,
-    0x503a31da,
-    0x503ab1f0,
-    0x503b3208,
-    0x503bb21a,
-    0x503c3236,
-    0x503cb24d,
-    0x503d3266,
-    0x503db27c,
-    0x503e3289,
-    0x503eb29f,
-    0x503f32b1,
+    0x503230c7,
+    0x5032b0d6,
+    0x503330e1,
+    0x5033b0f1,
+    0x5034310a,
+    0x5034b124,
+    0x50353132,
+    0x5035b148,
+    0x5036315a,
+    0x5036b170,
+    0x50373189,
+    0x5037b19c,
+    0x503831b4,
+    0x5038b1c5,
+    0x503931da,
+    0x5039b1ee,
+    0x503a320e,
+    0x503ab224,
+    0x503b323c,
+    0x503bb24e,
+    0x503c326a,
+    0x503cb281,
+    0x503d329a,
+    0x503db2b0,
+    0x503e32bd,
+    0x503eb2d3,
+    0x503f32e5,
     0x503f837b,
-    0x504032c4,
-    0x5040b2d4,
-    0x504132ee,
-    0x5041b2fd,
-    0x50423317,
-    0x5042b334,
-    0x50433344,
-    0x5043b354,
-    0x50443363,
+    0x504032f8,
+    0x5040b308,
+    0x50413322,
+    0x5041b331,
+    0x5042334b,
+    0x5042b368,
+    0x50433378,
+    0x5043b388,
+    0x50443397,
     0x50448431,
-    0x50453377,
-    0x5045b395,
-    0x504633a8,
-    0x5046b3be,
-    0x504733d0,
-    0x5047b3e5,
-    0x5048340b,
-    0x5048b419,
-    0x5049342c,
-    0x5049b441,
-    0x504a3457,
-    0x504ab467,
-    0x504b3487,
-    0x504bb49a,
-    0x504c34bd,
-    0x504cb4eb,
-    0x504d34fd,
-    0x504db51a,
-    0x504e3535,
-    0x504eb551,
-    0x504f3563,
-    0x504fb57a,
-    0x50503589,
+    0x504533ab,
+    0x5045b3c9,
+    0x504633dc,
+    0x5046b3f2,
+    0x50473404,
+    0x5047b419,
+    0x5048343f,
+    0x5048b44d,
+    0x50493460,
+    0x5049b475,
+    0x504a348b,
+    0x504ab49b,
+    0x504b34bb,
+    0x504bb4ce,
+    0x504c34f1,
+    0x504cb51f,
+    0x504d3531,
+    0x504db54e,
+    0x504e3569,
+    0x504eb585,
+    0x504f3597,
+    0x504fb5ae,
+    0x505035bd,
     0x505086f1,
-    0x5051359c,
+    0x505135d0,
     0x58320f52,
     0x68320f14,
     0x68328c6c,
@@ -1201,6 +1203,7 @@
     "PSK_IDENTITY_NOT_FOUND\0"
     "PSK_NO_CLIENT_CB\0"
     "PSK_NO_SERVER_CB\0"
+    "QUIC_INTERNAL_ERROR\0"
     "READ_TIMEOUT_EXPIRED\0"
     "RECORD_LENGTH_MISMATCH\0"
     "RECORD_TOO_LARGE\0"
@@ -1291,6 +1294,7 @@
     "WRONG_CERTIFICATE_TYPE\0"
     "WRONG_CIPHER_RETURNED\0"
     "WRONG_CURVE\0"
+    "WRONG_ENCRYPTION_LEVEL_RECEIVED\0"
     "WRONG_MESSAGE_TYPE\0"
     "WRONG_SIGNATURE_TYPE\0"
     "WRONG_SSL_VERSION\0"
diff --git a/src/crypto/err/ssl.errordata b/src/crypto/err/ssl.errordata
index 9bc295d..171b9c7 100644
--- a/src/crypto/err/ssl.errordata
+++ b/src/crypto/err/ssl.errordata
@@ -128,6 +128,7 @@
 SSL,195,PSK_IDENTITY_NOT_FOUND
 SSL,196,PSK_NO_CLIENT_CB
 SSL,197,PSK_NO_SERVER_CB
+SSL,298,QUIC_INTERNAL_ERROR
 SSL,198,READ_TIMEOUT_EXPIRED
 SSL,199,RECORD_LENGTH_MISMATCH
 SSL,200,RECORD_TOO_LARGE
@@ -221,6 +222,7 @@
 SSL,241,WRONG_CERTIFICATE_TYPE
 SSL,242,WRONG_CIPHER_RETURNED
 SSL,243,WRONG_CURVE
+SSL,299,WRONG_ENCRYPTION_LEVEL_RECEIVED
 SSL,244,WRONG_MESSAGE_TYPE
 SSL,245,WRONG_SIGNATURE_TYPE
 SSL,246,WRONG_SSL_VERSION
diff --git a/src/crypto/fipsmodule/FIPS.md b/src/crypto/fipsmodule/FIPS.md
index 426b38e..a60e2bf 100644
--- a/src/crypto/fipsmodule/FIPS.md
+++ b/src/crypto/fipsmodule/FIPS.md
@@ -9,6 +9,7 @@
 BoringCrypto has undergone the following validations:
 
 1. 2017-06-15: certificate [#2964](https://csrc.nist.gov/Projects/Cryptographic-Module-Validation-Program/Certificate/2964), [security policy](/crypto/fipsmodule/policydocs/BoringCrypto-Security-Policy-20170615.docx) (in docx format).
+1. 2018-07-30: certificate [#3318](https://csrc.nist.gov/Projects/Cryptographic-Module-Validation-Program/Certificate/3318), [security policy](/crypto/fipsmodule/policydocs/BoringCrypto-Security-Policy-20180730.docx) (in docx format).
 
 ## Running CAVP tests
 
diff --git a/src/crypto/fipsmodule/policydocs/BoringCrypto-Security-Policy-20180730.docx b/src/crypto/fipsmodule/policydocs/BoringCrypto-Security-Policy-20180730.docx
new file mode 100644
index 0000000..9574b1b
--- /dev/null
+++ b/src/crypto/fipsmodule/policydocs/BoringCrypto-Security-Policy-20180730.docx
Binary files differ
diff --git a/src/crypto/fipsmodule/rand/rand.c b/src/crypto/fipsmodule/rand/rand.c
index 02e63bc..e6b4bb4 100644
--- a/src/crypto/fipsmodule/rand/rand.c
+++ b/src/crypto/fipsmodule/rand/rand.c
@@ -109,8 +109,9 @@
   // next forms a NULL-terminated linked-list of all free |rand_state| objects.
   struct rand_state *next;
   // calls is the number of generate calls made on |drbg| since it was last
-  // (re)seeded. This is bound by |kReseedInterval|.
-  unsigned calls;
+  // (re)seeded. This is bound by
+  // |kReseedInterval - 1 + SIZE_MAX / CTR_DRBG_MAX_GENERATE_LENGTH|.
+  size_t calls;
 
 #if defined(BORINGSSL_FIPS)
   // next_all forms another NULL-terminated linked-list, this time of all
@@ -351,6 +352,8 @@
 
     out += todo;
     out_len -= todo;
+    // Though we only check before entering the loop, this cannot add enough to
+    // overflow a |size_t|.
     state->calls++;
     first_call = 0;
   }
diff --git a/src/include/openssl/base.h b/src/include/openssl/base.h
index f8567e0..7fe232f 100644
--- a/src/include/openssl/base.h
+++ b/src/include/openssl/base.h
@@ -394,6 +394,7 @@
 typedef struct ssl_ctx_st SSL_CTX;
 typedef struct ssl_method_st SSL_METHOD;
 typedef struct ssl_private_key_method_st SSL_PRIVATE_KEY_METHOD;
+typedef struct ssl_quic_method_st SSL_QUIC_METHOD;
 typedef struct ssl_session_st SSL_SESSION;
 typedef struct ssl_st SSL;
 typedef struct ssl_ticket_aead_method_st SSL_TICKET_AEAD_METHOD;
diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h
index 0f3d174..0fa10b5 100644
--- a/src/include/openssl/ssl.h
+++ b/src/include/openssl/ssl.h
@@ -3036,6 +3036,116 @@
                                                        size_t *out_params_len);
 
 
+// QUIC integration.
+//
+// QUIC acts as an underlying transport for the TLS 1.3 handshake. The following
+// functions allow a QUIC implementation to serve as the underlying transport as
+// described in draft-ietf-quic-tls.
+//
+// When configured for QUIC, |SSL_do_handshake| will drive the handshake as
+// before, but it will not use the configured |BIO|. It will call functions on
+// |SSL_QUIC_METHOD| to configure secrets and send data. If data is needed from
+// the peer, it will return |SSL_ERROR_WANT_READ|. When received, the caller
+// should call |SSL_provide_quic_data| and then |SSL_do_handshake| to continue
+// the handshake. It is an error to call |SSL_read| and |SSL_write| in QUIC.
+//
+// Note that secrets for an encryption level may be available to QUIC before the
+// level is active in TLS. Callers should use |SSL_quic_read_level| to determine
+// the active read level for |SSL_provide_quic_data|. |SSL_do_handshake| will
+// pass the active write level to |SSL_QUIC_METHOD| when writing data. Callers
+// can use |SSL_quic_write_level| to query the active write level when
+// generating their own errors.
+//
+// See https://tools.ietf.org/html/draft-ietf-quic-tls-15#section-4.1 for more
+// details.
+//
+// To avoid DoS attacks, the QUIC implementation must limit the amount of data
+// being queued up. The implementation can call
+// |SSL_quic_max_handshake_flight_len| to get the maximum buffer length at each
+// encryption level.
+//
+// Note: 0-RTT and post-handshake tickets are not currently supported via this
+// API.
+
+// ssl_encryption_level_t represents a specific QUIC encryption level used to
+// transmit handshake messages.
+enum ssl_encryption_level_t {
+  ssl_encryption_initial = 0,
+  ssl_encryption_early_data,
+  ssl_encryption_handshake,
+  ssl_encryption_application,
+};
+
+// ssl_quic_method_st (aka |SSL_QUIC_METHOD|) describes custom QUIC hooks.
+struct ssl_quic_method_st {
+  // set_encryption_secrets configures the read and write secrets for the given
+  // encryption level. This function will always be called before an encryption
+  // level other than |ssl_encryption_initial| is used. Note, however, that
+  // secrets for a level may be configured before TLS is ready to send or accept
+  // data at that level.
+  //
+  // When reading packets at a given level, the QUIC implementation must send
+  // ACKs at the same level, so this function provides read and write secrets
+  // together. The exception is |ssl_encryption_early_data|, where secrets are
+  // only available in the client to server direction. The other secret will be
+  // NULL. The server acknowledges such data at |ssl_encryption_application|,
+  // which will be configured in the same |SSL_do_handshake| call.
+  //
+  // This function should use |SSL_get_current_cipher| to determine the TLS
+  // cipher suite.
+  //
+  // It returns one on success and zero on error.
+  int (*set_encryption_secrets)(SSL *ssl, enum ssl_encryption_level_t level,
+                                const uint8_t *read_secret,
+                                const uint8_t *write_secret, size_t secret_len);
+  // add_handshake_data adds handshake data to the current flight at the given
+  // encryption level. It returns one on success and zero on error.
+  //
+  // BoringSSL will pack data from a single encryption level together, but a
+  // single handshake flight may include multiple encryption levels. Callers
+  // should defer writing data to the network until |flush_flight| to better
+  // pack QUIC packets into transport datagrams.
+  int (*add_handshake_data)(SSL *ssl, enum ssl_encryption_level_t level,
+                            const uint8_t *data, size_t len);
+  // flush_flight is called when the current flight is complete and should be
+  // written to the transport. Note a flight may contain data at several
+  // encryption levels. It returns one on success and zero on error.
+  int (*flush_flight)(SSL *ssl);
+  // send_alert sends a fatal alert at the specified encryption level. It
+  // returns one on success and zero on error.
+  int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level, uint8_t alert);
+};
+
+// SSL_quic_max_handshake_flight_len returns returns the maximum number of bytes
+// that may be received at the given encryption level. This function should be
+// used to limit buffering in the QUIC implementation.
+//
+// See https://tools.ietf.org/html/draft-ietf-quic-transport-16#section-4.4.
+OPENSSL_EXPORT size_t SSL_quic_max_handshake_flight_len(
+    const SSL *ssl, enum ssl_encryption_level_t level);
+
+// SSL_quic_read_level returns the current read encryption level.
+OPENSSL_EXPORT enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl);
+
+// SSL_quic_write_level returns the current write encryption level.
+OPENSSL_EXPORT enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl);
+
+// SSL_provide_quic_data provides data from QUIC at a particular encryption
+// level |level|. It is an error to call this function outside of the handshake
+// or with an encryption level other than the current read level. It returns one
+// on success and zero on error.
+OPENSSL_EXPORT int SSL_provide_quic_data(SSL *ssl,
+                                         enum ssl_encryption_level_t level,
+                                         const uint8_t *data, size_t len);
+
+
+// SSL_CTX_set_quic_method configures the QUIC hooks. This should only be
+// configured with a minimum version of TLS 1.3. |quic_method| must remain valid
+// for the lifetime of |ctx|. It returns one on success and zero on error.
+OPENSSL_EXPORT int SSL_CTX_set_quic_method(SSL_CTX *ctx,
+                                           const SSL_QUIC_METHOD *quic_method);
+
+
 // Early data.
 //
 // WARNING: 0-RTT support in BoringSSL is currently experimental and not fully
@@ -4795,6 +4905,8 @@
 #define SSL_R_INVALID_SIGNATURE_ALGORITHM 295
 #define SSL_R_DUPLICATE_SIGNATURE_ALGORITHM 296
 #define SSL_R_TLS13_DOWNGRADE 297
+#define SSL_R_QUIC_INTERNAL_ERROR 298
+#define SSL_R_WRONG_ENCRYPTION_LEVEL_RECEIVED 299
 #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
 #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
 #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020
diff --git a/src/ssl/handoff.cc b/src/ssl/handoff.cc
index a47b7c1..a49d289 100644
--- a/src/ssl/handoff.cc
+++ b/src/ssl/handoff.cc
@@ -24,6 +24,22 @@
 constexpr int kHandoffVersion = 0;
 constexpr int kHandbackVersion = 0;
 
+// serialize_features adds a description of features supported by this binary to
+// |out|.  Returns true on success and false on error.
+static bool serialize_features(CBB *out) {
+  CBB ciphers;
+  if (!CBB_add_asn1(out, &ciphers, CBS_ASN1_OCTETSTRING)) {
+    return false;
+  }
+  Span<const SSL_CIPHER> all_ciphers = AllCiphers();
+  for (const SSL_CIPHER& cipher : all_ciphers) {
+    if (!CBB_add_u16(&ciphers, static_cast<uint16_t>(cipher.id))) {
+      return false;
+    }
+  }
+  return CBB_flush(out);
+}
+
 bool SSL_serialize_handoff(const SSL *ssl, CBB *out) {
   const SSL3_STATE *const s3 = ssl->s3;
   if (!ssl->server ||
@@ -40,6 +56,7 @@
       !CBB_add_asn1_octet_string(&seq,
                                  reinterpret_cast<uint8_t *>(s3->hs_buf->data),
                                  s3->hs_buf->length) ||
+      !serialize_features(&seq) ||
       !CBB_flush(out)) {
     return false;
   }
@@ -59,6 +76,53 @@
   return true;
 }
 
+// apply_remote_features reads a list of supported features from |in| and
+// (possibly) reconfigures |ssl| to disallow the negotation of features whose
+// support has not been indicated.  (This prevents the the handshake from
+// committing to features that are not supported on the handoff/handback side.)
+static bool apply_remote_features(SSL *ssl, CBS *in) {
+  CBS ciphers;
+  if (!CBS_get_asn1(in, &ciphers, CBS_ASN1_OCTETSTRING)) {
+    return false;
+  }
+  bssl::UniquePtr<STACK_OF(SSL_CIPHER)> supported(sk_SSL_CIPHER_new_null());
+  while (CBS_len(&ciphers)) {
+    uint16_t id;
+    if (!CBS_get_u16(&ciphers, &id)) {
+      return false;
+    }
+    const SSL_CIPHER *cipher = SSL_get_cipher_by_value(id);
+    if (!cipher) {
+      continue;
+    }
+    if (!sk_SSL_CIPHER_push(supported.get(), cipher)) {
+      return false;
+    }
+  }
+  STACK_OF(SSL_CIPHER) *configured =
+      ssl->config->cipher_list ? ssl->config->cipher_list->ciphers.get()
+                               : ssl->ctx->cipher_list->ciphers.get();
+  bssl::UniquePtr<STACK_OF(SSL_CIPHER)> unsupported(sk_SSL_CIPHER_new_null());
+  for (const SSL_CIPHER *configured_cipher : configured) {
+    if (sk_SSL_CIPHER_find(supported.get(), nullptr, configured_cipher)) {
+      continue;
+    }
+    if (!sk_SSL_CIPHER_push(unsupported.get(), configured_cipher)) {
+      return false;
+    }
+  }
+  if (sk_SSL_CIPHER_num(unsupported.get()) && !ssl->config->cipher_list) {
+    ssl->config->cipher_list = bssl::MakeUnique<SSLCipherPreferenceList>();
+    if (!ssl->config->cipher_list->Init(*ssl->ctx->cipher_list)) {
+      return false;
+    }
+  }
+  for (const SSL_CIPHER *unsupported_cipher : unsupported.get()) {
+    ssl->config->cipher_list->Remove(unsupported_cipher);
+  }
+  return sk_SSL_CIPHER_num(SSL_get_ciphers(ssl)) > 0;
+}
+
 bool SSL_apply_handoff(SSL *ssl, Span<const uint8_t> handoff) {
   if (ssl->method->is_dtls) {
     return false;
@@ -74,7 +138,8 @@
 
   CBS transcript, hs_buf;
   if (!CBS_get_asn1(&seq, &transcript, CBS_ASN1_OCTETSTRING) ||
-      !CBS_get_asn1(&seq, &hs_buf, CBS_ASN1_OCTETSTRING)) {
+      !CBS_get_asn1(&seq, &hs_buf, CBS_ASN1_OCTETSTRING) ||
+      !apply_remote_features(ssl, &seq)) {
     return false;
   }
 
diff --git a/src/ssl/handshake.cc b/src/ssl/handshake.cc
index 963038f..b1da056 100644
--- a/src/ssl/handshake.cc
+++ b/src/ssl/handshake.cc
@@ -543,6 +543,16 @@
       case ssl_hs_read_server_hello:
       case ssl_hs_read_message:
       case ssl_hs_read_change_cipher_spec: {
+        if (ssl->ctx->quic_method) {
+          hs->wait = ssl_hs_ok;
+          // The change cipher spec is omitted in QUIC.
+          if (hs->wait != ssl_hs_read_change_cipher_spec) {
+            ssl->s3->rwstate = SSL_READING;
+            return -1;
+          }
+          break;
+        }
+
         uint8_t alert = SSL_AD_DECODE_ERROR;
         size_t consumed = 0;
         ssl_open_record_t ret;
diff --git a/src/ssl/handshake_client.cc b/src/ssl/handshake_client.cc
index e46b39f..24331ba 100644
--- a/src/ssl/handshake_client.cc
+++ b/src/ssl/handshake_client.cc
@@ -459,8 +459,8 @@
   if (!tls13_init_early_key_schedule(hs, ssl->session->master_key,
                                      ssl->session->master_key_length) ||
       !tls13_derive_early_secrets(hs) ||
-      !tls13_set_traffic_key(ssl, evp_aead_seal, hs->early_traffic_secret,
-                             hs->hash_len)) {
+      !tls13_set_traffic_key(ssl, ssl_encryption_early_data, evp_aead_seal,
+                             hs->early_traffic_secret, hs->hash_len)) {
     return ssl_hs_error;
   }
 
diff --git a/src/ssl/internal.h b/src/ssl/internal.h
index 2218992..2e05ee0 100644
--- a/src/ssl/internal.h
+++ b/src/ssl/internal.h
@@ -509,11 +509,17 @@
 
   bool Init(UniquePtr<STACK_OF(SSL_CIPHER)> ciphers,
             Span<const bool> in_group_flags);
+  bool Init(const SSLCipherPreferenceList &);
+
+  void Remove(const SSL_CIPHER *cipher);
 
   UniquePtr<STACK_OF(SSL_CIPHER)> ciphers;
   bool *in_group_flags = nullptr;
 };
 
+// AllCiphers returns an array of all supported ciphers, sorted by id.
+Span<const SSL_CIPHER> AllCiphers();
+
 // ssl_cipher_get_evp_aead sets |*out_aead| to point to the correct EVP_AEAD
 // object for |cipher| protocol version |version|. It sets |*out_mac_secret_len|
 // and |*out_fixed_iv_len| to the MAC key length and fixed IV length,
@@ -665,6 +671,12 @@
                                           Span<const uint8_t> mac_key,
                                           Span<const uint8_t> fixed_iv);
 
+  // CreatePlaceholderForQUIC creates a placeholder |SSLAEADContext| for the
+  // given cipher and version. The resulting object can be queried for various
+  // properties but cannot encrypt or decrypt data.
+  static UniquePtr<SSLAEADContext> CreatePlaceholderForQUIC(
+      uint16_t version, const SSL_CIPHER *cipher);
+
   // SetVersionIfNullCipher sets the version the SSLAEADContext for the null
   // cipher, to make version-specific determinations in the record layer prior
   // to a cipher being selected.
@@ -1231,7 +1243,8 @@
 
 // tls13_set_traffic_key sets the read or write traffic keys to
 // |traffic_secret|. It returns true on success and false on error.
-bool tls13_set_traffic_key(SSL *ssl, enum evp_aead_direction_t direction,
+bool tls13_set_traffic_key(SSL *ssl, enum ssl_encryption_level_t level,
+                           enum evp_aead_direction_t direction,
                            const uint8_t *traffic_secret,
                            size_t traffic_secret_len);
 
@@ -1272,7 +1285,8 @@
 // tls13_derive_session_psk calculates the PSK for this session based on the
 // resumption master secret and |nonce|. It returns true on success, and false
 // on failure.
-bool tls13_derive_session_psk(SSL_SESSION *session, Span<const uint8_t> nonce);
+bool tls13_derive_session_psk(SSL_SESSION *session, Span<const uint8_t> nonce,
+                              bool use_quic);
 
 // tls13_write_psk_binder calculates the PSK binder value and replaces the last
 // bytes of |msg| with the resulting value. It returns true on success, and
@@ -2074,6 +2088,9 @@
   // needs re-doing when in SSL_accept or SSL_connect
   int rwstate = SSL_NOTHING;
 
+  enum ssl_encryption_level_t read_level = ssl_encryption_initial;
+  enum ssl_encryption_level_t write_level = ssl_encryption_initial;
+
   // early_data_skipped is the amount of early data that has been skipped by the
   // record layer.
   uint16_t early_data_skipped = 0;
@@ -2790,6 +2807,9 @@
   // and is further constrainted by |SSL_OP_NO_*|.
   uint16_t conf_min_version = 0;
 
+  // quic_method is the method table corresponding to the QUIC hooks.
+  const SSL_QUIC_METHOD *quic_method = nullptr;
+
   // tls13_variant is the variant of TLS 1.3 we are using for this
   // configuration.
   tls13_variant_t tls13_variant = tls13_rfc;
diff --git a/src/ssl/s3_both.cc b/src/ssl/s3_both.cc
index 3f09d50..55e9aaa 100644
--- a/src/ssl/s3_both.cc
+++ b/src/ssl/s3_both.cc
@@ -193,8 +193,9 @@
   //
   // TODO(davidben): See if we can do this uniformly.
   Span<const uint8_t> rest = msg;
-  if (ssl->s3->aead_write_ctx->is_null_cipher() ||
-      ssl->version == TLS1_3_DRAFT23_VERSION) {
+  if (ssl->ctx->quic_method == nullptr &&
+      (ssl->s3->aead_write_ctx->is_null_cipher() ||
+       ssl->version == TLS1_3_DRAFT23_VERSION)) {
     while (!rest.empty()) {
       Span<const uint8_t> chunk = rest.subspan(0, ssl->max_send_fragment);
       rest = rest.subspan(chunk.size());
@@ -246,16 +247,29 @@
   }
 
   UniquePtr<BUF_MEM> pending_hs_data = std::move(ssl->s3->pending_hs_data);
-  return add_record_to_flight(
-      ssl, SSL3_RT_HANDSHAKE,
+  auto data =
       MakeConstSpan(reinterpret_cast<const uint8_t *>(pending_hs_data->data),
-                    pending_hs_data->length));
+                    pending_hs_data->length);
+  if (ssl->ctx->quic_method) {
+    if (!ssl->ctx->quic_method->add_handshake_data(ssl, ssl->s3->write_level,
+                                                   data.data(), data.size())) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR);
+      return false;
+    }
+    return true;
+  }
+
+  return add_record_to_flight(ssl, SSL3_RT_HANDSHAKE, data);
 }
 
 bool ssl3_add_change_cipher_spec(SSL *ssl) {
   static const uint8_t kChangeCipherSpec[1] = {SSL3_MT_CCS};
 
-  if (!tls_flush_pending_hs_data(ssl) ||
+  if (!tls_flush_pending_hs_data(ssl)) {
+    return false;
+  }
+
+  if (!ssl->ctx->quic_method &&
       !add_record_to_flight(ssl, SSL3_RT_CHANGE_CIPHER_SPEC,
                             kChangeCipherSpec)) {
     return false;
@@ -271,6 +285,18 @@
     return -1;
   }
 
+  if (ssl->ctx->quic_method) {
+    if (ssl->s3->write_shutdown != ssl_shutdown_none) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_PROTOCOL_IS_SHUTDOWN);
+      return -1;
+    }
+
+    if (!ssl->ctx->quic_method->flush_flight(ssl)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR);
+      return -1;
+    }
+  }
+
   if (ssl->s3->pending_flight == nullptr) {
     return 1;
   }
diff --git a/src/ssl/s3_pkt.cc b/src/ssl/s3_pkt.cc
index 1ccbf9f..e9b652e 100644
--- a/src/ssl/s3_pkt.cc
+++ b/src/ssl/s3_pkt.cc
@@ -163,9 +163,11 @@
   for (;;) {
     // max contains the maximum number of bytes that we can put into a record.
     unsigned max = ssl->max_send_fragment;
-    if (is_early_data_write && max > ssl->session->ticket_max_early_data -
-                                         ssl->s3->hs->early_data_written) {
-      max = ssl->session->ticket_max_early_data - ssl->s3->hs->early_data_written;
+    if (is_early_data_write &&
+        max > ssl->session->ticket_max_early_data -
+                  ssl->s3->hs->early_data_written) {
+      max =
+          ssl->session->ticket_max_early_data - ssl->s3->hs->early_data_written;
       if (max == 0) {
         ssl->s3->wnum = tot;
         ssl->s3->hs->can_early_write = false;
@@ -406,10 +408,19 @@
 }
 
 int ssl3_dispatch_alert(SSL *ssl) {
-  int ret = do_ssl3_write(ssl, SSL3_RT_ALERT, &ssl->s3->send_alert[0], 2);
-  if (ret <= 0) {
-    return ret;
+  if (ssl->ctx->quic_method) {
+    if (!ssl->ctx->quic_method->send_alert(ssl, ssl->s3->write_level,
+                                           ssl->s3->send_alert[1])) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR);
+      return 0;
+    }
+  } else {
+    int ret = do_ssl3_write(ssl, SSL3_RT_ALERT, &ssl->s3->send_alert[0], 2);
+    if (ret <= 0) {
+      return ret;
+    }
   }
+
   ssl->s3->alert_dispatch = 0;
 
   // If the alert is fatal, flush the BIO now.
diff --git a/src/ssl/ssl_aead_ctx.cc b/src/ssl/ssl_aead_ctx.cc
index 335f6f4..f01b57d 100644
--- a/src/ssl/ssl_aead_ctx.cc
+++ b/src/ssl/ssl_aead_ctx.cc
@@ -151,6 +151,11 @@
   return aead_ctx;
 }
 
+UniquePtr<SSLAEADContext> SSLAEADContext::CreatePlaceholderForQUIC(
+    uint16_t version, const SSL_CIPHER *cipher) {
+  return MakeUnique<SSLAEADContext>(version, false, cipher);
+}
+
 void SSLAEADContext::SetVersionIfNullCipher(uint16_t version) {
   if (is_null_cipher()) {
     version_ = version;
diff --git a/src/ssl/ssl_cipher.cc b/src/ssl/ssl_cipher.cc
index 0ed91d6..a420f4d 100644
--- a/src/ssl/ssl_cipher.cc
+++ b/src/ssl/ssl_cipher.cc
@@ -156,7 +156,6 @@
 
 BSSL_NAMESPACE_BEGIN
 
-// kCiphers is an array of all supported ciphers, sorted by id.
 static constexpr SSL_CIPHER kCiphers[] = {
     // The RSA ciphers
     // Cipher 02
@@ -464,7 +463,9 @@
 
 };
 
-static const size_t kCiphersLen = OPENSSL_ARRAY_SIZE(kCiphers);
+Span<const SSL_CIPHER> AllCiphers() {
+  return MakeConstSpan(kCiphers, OPENSSL_ARRAY_SIZE(kCiphers));
+}
 
 #define CIPHER_ADD 1
 #define CIPHER_KILL 2
@@ -707,7 +708,7 @@
                                        CIPHER_ORDER **out_head,
                                        CIPHER_ORDER **out_tail) {
   Array<CIPHER_ORDER> co_list;
-  if (!co_list.Init(kCiphersLen)) {
+  if (!co_list.Init(OPENSSL_ARRAY_SIZE(kCiphers))) {
     return false;
   }
 
@@ -772,6 +773,31 @@
   return true;
 }
 
+bool SSLCipherPreferenceList::Init(const SSLCipherPreferenceList& other) {
+  size_t size = sk_SSL_CIPHER_num(other.ciphers.get());
+  Span<const bool> other_flags(other.in_group_flags, size);
+  UniquePtr<STACK_OF(SSL_CIPHER)> other_ciphers(sk_SSL_CIPHER_dup(
+      other.ciphers.get()));
+  if (!other_ciphers) {
+    return false;
+  }
+  return Init(std::move(other_ciphers), other_flags);
+}
+
+void SSLCipherPreferenceList::Remove(const SSL_CIPHER *cipher) {
+  size_t index;
+  if (!sk_SSL_CIPHER_find(ciphers.get(), &index, cipher)) {
+    return;
+  }
+  if (!in_group_flags[index] /* last element of group */ && index > 0) {
+    in_group_flags[index-1] = false;
+  }
+  for (size_t i = index; i < sk_SSL_CIPHER_num(ciphers.get()) - 1; ++i) {
+    in_group_flags[i] = in_group_flags[i+1];
+  }
+  sk_SSL_CIPHER_delete(ciphers.get(), index);
+}
+
 // ssl_cipher_apply_rule applies the rule type |rule| to ciphers matching its
 // parameters in the linked list from |*head_p| to |*tail_p|. It writes the new
 // head and tail of the list to |*head_p| and |*tail_p|, respectively.
@@ -1051,7 +1077,7 @@
       // Look for a matching exact cipher. These aren't allowed in multipart
       // rules.
       if (!multi && ch != '+') {
-        for (j = 0; j < kCiphersLen; j++) {
+        for (j = 0; j < OPENSSL_ARRAY_SIZE(kCiphers); j++) {
           const SSL_CIPHER *cipher = &kCiphers[j];
           if (rule_equals(cipher->name, buf, buf_len) ||
               rule_equals(cipher->standard_name, buf, buf_len)) {
@@ -1217,7 +1243,7 @@
   UniquePtr<STACK_OF(SSL_CIPHER)> cipherstack(sk_SSL_CIPHER_new_null());
   Array<bool> in_group_flags;
   if (cipherstack == nullptr ||
-      !in_group_flags.Init(kCiphersLen)) {
+      !in_group_flags.Init(OPENSSL_ARRAY_SIZE(kCiphers))) {
     return false;
   }
 
@@ -1345,7 +1371,8 @@
 
   c.id = 0x03000000L | value;
   return reinterpret_cast<const SSL_CIPHER *>(bsearch(
-      &c, kCiphers, kCiphersLen, sizeof(SSL_CIPHER), ssl_cipher_id_cmp));
+      &c, kCiphers, OPENSSL_ARRAY_SIZE(kCiphers), sizeof(SSL_CIPHER),
+      ssl_cipher_id_cmp));
 }
 
 uint32_t SSL_CIPHER_get_id(const SSL_CIPHER *cipher) { return cipher->id; }
diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc
index 1f64865..5bd2442 100644
--- a/src/ssl/ssl_lib.cc
+++ b/src/ssl/ssl_lib.cc
@@ -781,6 +781,82 @@
 
 BIO *SSL_get_wbio(const SSL *ssl) { return ssl->wbio.get(); }
 
+size_t SSL_quic_max_handshake_flight_len(const SSL *ssl,
+                                         enum ssl_encryption_level_t level) {
+  // Limits flights to 16K by default when there are no large
+  // (certificate-carrying) messages.
+  static const size_t kDefaultLimit = 16384;
+
+  switch (level) {
+    case ssl_encryption_initial:
+      return kDefaultLimit;
+    case ssl_encryption_early_data:
+      // QUIC does not send EndOfEarlyData.
+      return 0;
+    case ssl_encryption_handshake:
+      if (ssl->server) {
+        // Servers may receive Certificate message if configured to request
+        // client certificates.
+        if (!!(ssl->config->verify_mode & SSL_VERIFY_PEER) &&
+            ssl->max_cert_list > kDefaultLimit) {
+          return ssl->max_cert_list;
+        }
+      } else {
+        // Clients may receive both Certificate message and a CertificateRequest
+        // message.
+        if (2*ssl->max_cert_list > kDefaultLimit) {
+          return 2*ssl->max_cert_list;
+        }
+      }
+      return kDefaultLimit;
+    case ssl_encryption_application:
+      // Note there is not actually a bound on the number of NewSessionTickets
+      // one may send in a row. This level may need more involved flow
+      // control. See https://github.com/quicwg/base-drafts/issues/1834.
+      return kDefaultLimit;
+  }
+
+  return 0;
+}
+
+enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl) {
+  return ssl->s3->read_level;
+}
+
+enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl) {
+  return ssl->s3->write_level;
+}
+
+int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level,
+                          const uint8_t *data, size_t len) {
+  if (ssl->ctx->quic_method == nullptr) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  if (level != ssl->s3->read_level) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_ENCRYPTION_LEVEL_RECEIVED);
+    return 0;
+  }
+
+  size_t new_len = (ssl->s3->hs_buf ? ssl->s3->hs_buf->length : 0) + len;
+  if (new_len < len ||
+      new_len > SSL_quic_max_handshake_flight_len(ssl, level)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_EXCESSIVE_MESSAGE_SIZE);
+    return 0;
+  }
+
+  // Re-create the handshake buffer if needed.
+  if (!ssl->s3->hs_buf) {
+    ssl->s3->hs_buf.reset(BUF_MEM_new());
+    if (!ssl->s3->hs_buf) {
+      return 0;
+    }
+  }
+
+  return BUF_MEM_append(ssl->s3->hs_buf.get(), data, len);
+}
+
 int SSL_do_handshake(SSL *ssl) {
   ssl_reset_error_state(ssl);
 
@@ -961,6 +1037,11 @@
 }
 
 int SSL_peek(SSL *ssl, void *buf, int num) {
+  if (ssl->ctx->quic_method != nullptr) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
   int ret = ssl_read_impl(ssl);
   if (ret <= 0) {
     return ret;
@@ -977,6 +1058,11 @@
 int SSL_write(SSL *ssl, const void *buf, int num) {
   ssl_reset_error_state(ssl);
 
+  if (ssl->ctx->quic_method != nullptr) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
   if (ssl->do_handshake == NULL) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);
     return -1;
@@ -1193,6 +1279,9 @@
       return SSL_ERROR_HANDBACK;
 
     case SSL_READING: {
+      if (ssl->ctx->quic_method) {
+        return SSL_ERROR_WANT_READ;
+      }
       BIO *bio = SSL_get_rbio(ssl);
       if (BIO_should_read(bio)) {
         return SSL_ERROR_WANT_READ;
@@ -2298,6 +2387,14 @@
   return buf;
 }
 
+int SSL_CTX_set_quic_method(SSL_CTX *ctx, const SSL_QUIC_METHOD *quic_method) {
+  if (ctx->method->is_dtls) {
+    return 0;
+  }
+  ctx->quic_method = quic_method;
+  return 1;
+}
+
 int SSL_get_ex_new_index(long argl, void *argp, CRYPTO_EX_unused *unused,
                          CRYPTO_EX_dup *dup_unused, CRYPTO_EX_free *free_func) {
   int index;
diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc
index 61a47d3..f7b299a 100644
--- a/src/ssl/ssl_test.cc
+++ b/src/ssl/ssl_test.cc
@@ -17,6 +17,7 @@
 #include <time.h>
 
 #include <algorithm>
+#include <limits>
 #include <string>
 #include <utility>
 #include <vector>
@@ -104,6 +105,26 @@
   std::vector<uint16_t> expected;
 };
 
+template <typename T>
+class UnownedSSLExData {
+ public:
+  UnownedSSLExData() {
+    index_ = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+  }
+
+  T *Get(const SSL *ssl) {
+    return index_ < 0 ? nullptr
+                      : static_cast<T *>(SSL_get_ex_data(ssl, index_));
+  }
+
+  bool Set(SSL *ssl, T *t) {
+    return index_ >= 0 && SSL_set_ex_data(ssl, index_, t);
+  }
+
+ private:
+  int index_;
+};
+
 static const CipherTest kCipherTests[] = {
     // Selecting individual ciphers should work.
     {
@@ -4257,6 +4278,35 @@
   }
 }
 
+TEST(SSLTest, ApplyHandoffRemovesUnsupportedCiphers) {
+  bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
+  bssl::UniquePtr<SSL> server(SSL_new(server_ctx.get()));
+
+  // handoff is a handoff message that has been artificially modified to pretend
+  // that only cipher 0x0A is supported.  When it is applied to |server|, all
+  // ciphers but that one should be removed.
+  uint8_t handoff[] = {
+      0x30, 0x81, 0x8e, 0x02, 0x01, 0x00, 0x04, 0x00, 0x04, 0x81, 0x82, 0x01,
+      0x00, 0x00, 0x7e, 0x03, 0x03, 0x77, 0x62, 0x00, 0x9a, 0x13, 0x48, 0x23,
+      0x46, 0x11, 0x6c, 0x0b, 0x1c, 0x91, 0x4e, 0xbc, 0x1c, 0xff, 0x54, 0xb9,
+      0xe6, 0x3f, 0xa8, 0x8d, 0x49, 0x37, 0x7a, 0x9e, 0xbf, 0x36, 0xd5, 0x08,
+      0x24, 0x00, 0x00, 0x1e, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30,
+      0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x09, 0xc0, 0x13, 0xc0, 0x0a, 0xc0, 0x14,
+      0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x00, 0x0a, 0x01, 0x00,
+      0x00, 0x37, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+      0x23, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 0x08,
+      0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06,
+      0x01, 0x02, 0x01, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0a, 0x00,
+      0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x04, 0x02, 0x00,
+      0x0a,
+  };
+
+  EXPECT_EQ(20u, sk_SSL_CIPHER_num(SSL_get_ciphers(server.get())));
+  ASSERT_TRUE(
+      SSL_apply_handoff(server.get(), {handoff, OPENSSL_ARRAY_SIZE(handoff)}));
+  EXPECT_EQ(1u, sk_SSL_CIPHER_num(SSL_get_ciphers(server.get())));
+}
+
 TEST_P(SSLVersionTest, VerifyBeforeCertRequest) {
   // Configure the server to request client certificates.
   SSL_CTX_set_custom_verify(
@@ -4422,6 +4472,547 @@
 }
 #endif
 
+constexpr size_t kNumQUICLevels = 4;
+static_assert(ssl_encryption_initial < kNumQUICLevels,
+              "kNumQUICLevels is wrong");
+static_assert(ssl_encryption_early_data < kNumQUICLevels,
+              "kNumQUICLevels is wrong");
+static_assert(ssl_encryption_handshake < kNumQUICLevels,
+              "kNumQUICLevels is wrong");
+static_assert(ssl_encryption_application < kNumQUICLevels,
+              "kNumQUICLevels is wrong");
+
+class MockQUICTransport {
+ public:
+  MockQUICTransport() {
+    // The caller is expected to configure initial secrets.
+    levels_[ssl_encryption_initial].write_secret = {1};
+    levels_[ssl_encryption_initial].read_secret = {1};
+  }
+
+  void set_peer(MockQUICTransport *peer) { peer_ = peer; }
+
+  bool has_alert() const { return has_alert_; }
+  ssl_encryption_level_t alert_level() const { return alert_level_; }
+  uint8_t alert() const { return alert_; }
+
+  bool PeerSecretsMatch(ssl_encryption_level_t level) const {
+    return levels_[level].write_secret == peer_->levels_[level].read_secret &&
+           levels_[level].read_secret == peer_->levels_[level].write_secret;
+  }
+
+  bool HasSecrets(ssl_encryption_level_t level) const {
+    return !levels_[level].write_secret.empty() ||
+           !levels_[level].read_secret.empty();
+  }
+
+  bool SetEncryptionSecrets(ssl_encryption_level_t level,
+                            const uint8_t *read_secret,
+                            const uint8_t *write_secret, size_t secret_len) {
+    if (HasSecrets(level)) {
+      ADD_FAILURE() << "duplicate keys configured";
+      return false;
+    }
+    if (level != ssl_encryption_early_data &&
+        (read_secret == nullptr || write_secret == nullptr)) {
+      ADD_FAILURE() << "key was unexpectedly null";
+      return false;
+    }
+    if (read_secret != nullptr) {
+      levels_[level].read_secret.assign(read_secret, read_secret + secret_len);
+    }
+    if (write_secret != nullptr) {
+      levels_[level].write_secret.assign(write_secret,
+                                         write_secret + secret_len);
+    }
+    return true;
+  }
+
+  bool WriteHandshakeData(ssl_encryption_level_t level,
+                          Span<const uint8_t> data) {
+    if (levels_[level].write_secret.empty()) {
+      ADD_FAILURE() << "data written before keys configured";
+      return false;
+    }
+    levels_[level].write_data.insert(levels_[level].write_data.end(),
+                                     data.begin(), data.end());
+    return true;
+  }
+
+  bool SendAlert(ssl_encryption_level_t level, uint8_t alert_value) {
+    if (has_alert_) {
+      ADD_FAILURE() << "duplicate alert sent";
+      return false;
+    }
+
+    if (levels_[level].write_secret.empty()) {
+      ADD_FAILURE() << "alert sent before keys configured";
+      return false;
+    }
+
+    has_alert_ = true;
+    alert_level_ = level;
+    alert_ = alert_value;
+    return true;
+  }
+
+  bool ReadHandshakeData(std::vector<uint8_t> *out,
+                         ssl_encryption_level_t level,
+                         size_t num = std::numeric_limits<size_t>::max()) {
+    if (levels_[level].read_secret.empty()) {
+      ADD_FAILURE() << "data read before keys configured";
+      return false;
+    }
+    // The peer may not have configured any keys yet.
+    if (peer_->levels_[level].write_secret.empty()) {
+      return true;
+    }
+    // Check the peer computed the same key.
+    if (peer_->levels_[level].write_secret != levels_[level].read_secret) {
+      ADD_FAILURE() << "peer write key does not match read key";
+      return false;
+    }
+    std::vector<uint8_t> *peer_data = &peer_->levels_[level].write_data;
+    num = std::min(num, peer_data->size());
+    out->assign(peer_data->begin(), peer_data->begin() + num);
+    peer_data->erase(peer_data->begin(), peer_data->begin() + num);
+    return true;
+  }
+
+ private:
+  MockQUICTransport *peer_ = nullptr;
+
+  bool has_alert_ = false;
+  ssl_encryption_level_t alert_level_ = ssl_encryption_initial;
+  uint8_t alert_ = 0;
+
+  struct Level {
+    std::vector<uint8_t> write_data;
+    std::vector<uint8_t> write_secret;
+    std::vector<uint8_t> read_secret;
+  };
+  Level levels_[kNumQUICLevels];
+};
+
+class MockQUICTransportPair {
+ public:
+  MockQUICTransportPair() {
+    server_.set_peer(&client_);
+    client_.set_peer(&server_);
+  }
+
+  ~MockQUICTransportPair() {
+    server_.set_peer(nullptr);
+    client_.set_peer(nullptr);
+  }
+
+  MockQUICTransport *client() { return &client_; }
+  MockQUICTransport *server() { return &server_; }
+
+  bool SecretsMatch(ssl_encryption_level_t level) const {
+    return client_.PeerSecretsMatch(level);
+  }
+
+ private:
+  MockQUICTransport client_;
+  MockQUICTransport server_;
+};
+
+class QUICMethodTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    client_ctx_.reset(SSL_CTX_new(TLS_method()));
+    server_ctx_.reset(SSL_CTX_new(TLS_method()));
+    ASSERT_TRUE(client_ctx_);
+    ASSERT_TRUE(server_ctx_);
+
+    bssl::UniquePtr<X509> cert = GetTestCertificate();
+    bssl::UniquePtr<EVP_PKEY> key = GetTestKey();
+    ASSERT_TRUE(cert);
+    ASSERT_TRUE(key);
+    ASSERT_TRUE(SSL_CTX_use_certificate(server_ctx_.get(), cert.get()));
+    ASSERT_TRUE(SSL_CTX_use_PrivateKey(server_ctx_.get(), key.get()));
+
+    SSL_CTX_set_min_proto_version(server_ctx_.get(), TLS1_3_VERSION);
+    SSL_CTX_set_max_proto_version(server_ctx_.get(), TLS1_3_VERSION);
+    SSL_CTX_set_min_proto_version(client_ctx_.get(), TLS1_3_VERSION);
+    SSL_CTX_set_max_proto_version(client_ctx_.get(), TLS1_3_VERSION);
+  }
+
+  static MockQUICTransport *TransportFromSSL(const SSL *ssl) {
+    return ex_data_.Get(ssl);
+  }
+
+  static bool ProvideHandshakeData(
+      SSL *ssl, size_t num = std::numeric_limits<size_t>::max()) {
+    MockQUICTransport *transport = TransportFromSSL(ssl);
+    ssl_encryption_level_t level = SSL_quic_read_level(ssl);
+    std::vector<uint8_t> data;
+    return transport->ReadHandshakeData(&data, level, num) &&
+           SSL_provide_quic_data(ssl, level, data.data(), data.size());
+  }
+
+  bool CreateClientAndServer() {
+    client_.reset(SSL_new(client_ctx_.get()));
+    server_.reset(SSL_new(server_ctx_.get()));
+    if (!client_ || !server_) {
+      return false;
+    }
+
+    SSL_set_connect_state(client_.get());
+    SSL_set_accept_state(server_.get());
+
+    ex_data_.Set(client_.get(), transport_.client());
+    ex_data_.Set(server_.get(), transport_.server());
+    return true;
+  }
+
+  // The following functions may be configured on an |SSL_QUIC_METHOD| as
+  // default implementations.
+
+  static int SetEncryptionSecretsCallback(SSL *ssl,
+                                          ssl_encryption_level_t level,
+                                          const uint8_t *read_key,
+                                          const uint8_t *write_key,
+                                          size_t key_len) {
+    return TransportFromSSL(ssl)->SetEncryptionSecrets(level, read_key,
+                                                       write_key, key_len);
+  }
+
+  static int AddHandshakeDataCallback(SSL *ssl,
+                                      enum ssl_encryption_level_t level,
+                                      const uint8_t *data, size_t len) {
+    EXPECT_EQ(level, SSL_quic_write_level(ssl));
+    return TransportFromSSL(ssl)->WriteHandshakeData(level,
+                                                     MakeConstSpan(data, len));
+  }
+
+  static int FlushFlightCallback(SSL *ssl) { return 1; }
+
+  static int SendAlertCallback(SSL *ssl, ssl_encryption_level_t level,
+                               uint8_t alert) {
+    EXPECT_EQ(level, SSL_quic_write_level(ssl));
+    return TransportFromSSL(ssl)->SendAlert(level, alert);
+  }
+
+  bssl::UniquePtr<SSL_CTX> client_ctx_;
+  bssl::UniquePtr<SSL_CTX> server_ctx_;
+
+  static UnownedSSLExData<MockQUICTransport> ex_data_;
+  MockQUICTransportPair transport_;
+
+  bssl::UniquePtr<SSL> client_;
+  bssl::UniquePtr<SSL> server_;
+};
+
+UnownedSSLExData<MockQUICTransport> QUICMethodTest::ex_data_;
+
+// Test a full handshake works.
+TEST_F(QUICMethodTest, Basic) {
+  const SSL_QUIC_METHOD quic_method = {
+      SetEncryptionSecretsCallback,
+      AddHandshakeDataCallback,
+      FlushFlightCallback,
+      SendAlertCallback,
+  };
+
+  ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
+  ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
+  ASSERT_TRUE(CreateClientAndServer());
+
+  for (;;) {
+    ASSERT_TRUE(ProvideHandshakeData(client_.get()));
+    int client_ret = SSL_do_handshake(client_.get());
+    if (client_ret != 1) {
+      ASSERT_EQ(client_ret, -1);
+      ASSERT_EQ(SSL_get_error(client_.get(), client_ret), SSL_ERROR_WANT_READ);
+    }
+
+    ASSERT_TRUE(ProvideHandshakeData(server_.get()));
+    int server_ret = SSL_do_handshake(server_.get());
+    if (server_ret != 1) {
+      ASSERT_EQ(server_ret, -1);
+      ASSERT_EQ(SSL_get_error(server_.get(), server_ret), SSL_ERROR_WANT_READ);
+    }
+
+    if (client_ret == 1 && server_ret == 1) {
+      break;
+    }
+  }
+
+  EXPECT_EQ(SSL_do_handshake(client_.get()), 1);
+  EXPECT_EQ(SSL_do_handshake(server_.get()), 1);
+  EXPECT_TRUE(transport_.SecretsMatch(ssl_encryption_application));
+  EXPECT_FALSE(transport_.client()->has_alert());
+  EXPECT_FALSE(transport_.server()->has_alert());
+
+  // The server sent NewSessionTicket messages in the handshake.
+  //
+  // TODO(davidben,svaldez): Add an API for the client to consume post-handshake
+  // messages and update these tests.
+  std::vector<uint8_t> new_session_ticket;
+  ASSERT_TRUE(transport_.client()->ReadHandshakeData(
+      &new_session_ticket, ssl_encryption_application));
+  EXPECT_FALSE(new_session_ticket.empty());
+}
+
+// Test only releasing data to QUIC one byte at a time on request, to maximize
+// state machine pauses. Additionally, test that existing asynchronous callbacks
+// still work.
+TEST_F(QUICMethodTest, Async) {
+  const SSL_QUIC_METHOD quic_method = {
+      SetEncryptionSecretsCallback,
+      AddHandshakeDataCallback,
+      FlushFlightCallback,
+      SendAlertCallback,
+  };
+
+  ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
+  ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
+  ASSERT_TRUE(CreateClientAndServer());
+
+  // Install an asynchronous certificate callback.
+  bool cert_cb_ok = false;
+  SSL_set_cert_cb(server_.get(),
+                  [](SSL *, void *arg) -> int {
+                    return *static_cast<bool *>(arg) ? 1 : -1;
+                  },
+                  &cert_cb_ok);
+
+  for (;;) {
+    int client_ret = SSL_do_handshake(client_.get());
+    if (client_ret != 1) {
+      ASSERT_EQ(client_ret, -1);
+      ASSERT_EQ(SSL_get_error(client_.get(), client_ret), SSL_ERROR_WANT_READ);
+      ASSERT_TRUE(ProvideHandshakeData(client_.get(), 1));
+    }
+
+    int server_ret = SSL_do_handshake(server_.get());
+    if (server_ret != 1) {
+      ASSERT_EQ(server_ret, -1);
+      int ssl_err = SSL_get_error(server_.get(), server_ret);
+      switch (ssl_err) {
+        case SSL_ERROR_WANT_READ:
+          ASSERT_TRUE(ProvideHandshakeData(server_.get(), 1));
+          break;
+        case SSL_ERROR_WANT_X509_LOOKUP:
+          ASSERT_FALSE(cert_cb_ok);
+          cert_cb_ok = true;
+          break;
+        default:
+          FAIL() << "Unexpected SSL_get_error result: " << ssl_err;
+      }
+    }
+
+    if (client_ret == 1 && server_ret == 1) {
+      break;
+    }
+  }
+
+  EXPECT_EQ(SSL_do_handshake(client_.get()), 1);
+  EXPECT_EQ(SSL_do_handshake(server_.get()), 1);
+  EXPECT_TRUE(transport_.SecretsMatch(ssl_encryption_application));
+  EXPECT_FALSE(transport_.client()->has_alert());
+  EXPECT_FALSE(transport_.server()->has_alert());
+}
+
+// Test buffering write data until explicit flushes.
+TEST_F(QUICMethodTest, Buffered) {
+  struct BufferedFlight {
+    std::vector<uint8_t> data[kNumQUICLevels];
+  };
+  static UnownedSSLExData<BufferedFlight> buffered_flights;
+
+  auto add_handshake_data = [](SSL *ssl, enum ssl_encryption_level_t level,
+                               const uint8_t *data, size_t len) -> int {
+    BufferedFlight *flight = buffered_flights.Get(ssl);
+    flight->data[level].insert(flight->data[level].end(), data, data + len);
+    return 1;
+  };
+
+  auto flush_flight = [](SSL *ssl) -> int {
+    BufferedFlight *flight = buffered_flights.Get(ssl);
+    for (size_t level = 0; level < kNumQUICLevels; level++) {
+      if (!flight->data[level].empty()) {
+        if (!TransportFromSSL(ssl)->WriteHandshakeData(
+                static_cast<ssl_encryption_level_t>(level),
+                flight->data[level])) {
+          return 0;
+        }
+        flight->data[level].clear();
+      }
+    }
+    return 1;
+  };
+
+  const SSL_QUIC_METHOD quic_method = {
+    SetEncryptionSecretsCallback,
+    add_handshake_data,
+    flush_flight,
+    SendAlertCallback,
+  };
+
+  ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
+  ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
+  ASSERT_TRUE(CreateClientAndServer());
+
+  BufferedFlight client_flight, server_flight;
+  buffered_flights.Set(client_.get(), &client_flight);
+  buffered_flights.Set(server_.get(), &server_flight);
+
+  for (;;) {
+    ASSERT_TRUE(ProvideHandshakeData(client_.get()));
+    int client_ret = SSL_do_handshake(client_.get());
+    if (client_ret != 1) {
+      ASSERT_EQ(client_ret, -1);
+      ASSERT_EQ(SSL_get_error(client_.get(), client_ret), SSL_ERROR_WANT_READ);
+    }
+
+    ASSERT_TRUE(ProvideHandshakeData(server_.get()));
+    int server_ret = SSL_do_handshake(server_.get());
+    if (server_ret != 1) {
+      ASSERT_EQ(server_ret, -1);
+      ASSERT_EQ(SSL_get_error(server_.get(), server_ret), SSL_ERROR_WANT_READ);
+    }
+
+    if (client_ret == 1 && server_ret == 1) {
+      break;
+    }
+  }
+
+  EXPECT_EQ(SSL_do_handshake(client_.get()), 1);
+  EXPECT_EQ(SSL_do_handshake(server_.get()), 1);
+  EXPECT_TRUE(transport_.SecretsMatch(ssl_encryption_application));
+  EXPECT_FALSE(transport_.client()->has_alert());
+  EXPECT_FALSE(transport_.server()->has_alert());
+}
+
+// Test that excess data at one level is rejected. That is, if a single
+// |SSL_provide_quic_data| call included both ServerHello and
+// EncryptedExtensions in a single chunk, BoringSSL notices and rejects this on
+// key change.
+TEST_F(QUICMethodTest, ExcessProvidedData) {
+  auto add_handshake_data = [](SSL *ssl, enum ssl_encryption_level_t level,
+                               const uint8_t *data, size_t len) -> int {
+    // Switch everything to the initial level.
+    return TransportFromSSL(ssl)->WriteHandshakeData(ssl_encryption_initial,
+                                                     MakeConstSpan(data, len));
+  };
+
+  const SSL_QUIC_METHOD quic_method = {
+      SetEncryptionSecretsCallback,
+      add_handshake_data,
+      FlushFlightCallback,
+      SendAlertCallback,
+  };
+
+  ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
+  ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
+  ASSERT_TRUE(CreateClientAndServer());
+
+  // Send the ClientHello and ServerHello through Finished.
+  ASSERT_EQ(SSL_do_handshake(client_.get()), -1);
+  ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_WANT_READ);
+  ASSERT_TRUE(ProvideHandshakeData(server_.get()));
+  ASSERT_EQ(SSL_do_handshake(server_.get()), -1);
+  ASSERT_EQ(SSL_get_error(server_.get(), -1), SSL_ERROR_WANT_READ);
+
+  // The client is still waiting for the ServerHello at initial
+  // encryption.
+  ASSERT_EQ(ssl_encryption_initial, SSL_quic_read_level(client_.get()));
+
+  // |add_handshake_data| incorrectly wrote everything at the initial level, so
+  // this queues up ServerHello through Finished in one chunk.
+  ASSERT_TRUE(ProvideHandshakeData(client_.get()));
+
+  // The client reads ServerHello successfully, but then rejects the buffered
+  // EncryptedExtensions on key change.
+  ASSERT_EQ(SSL_do_handshake(client_.get()), -1);
+  ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_SSL);
+  uint32_t err = ERR_get_error();
+  EXPECT_EQ(ERR_GET_LIB(err), ERR_LIB_SSL);
+  EXPECT_EQ(ERR_GET_REASON(err), SSL_R_BUFFERED_MESSAGES_ON_CIPHER_CHANGE);
+
+  // The client sends an alert in response to this.
+  ASSERT_TRUE(transport_.client()->has_alert());
+  EXPECT_EQ(transport_.client()->alert_level(), ssl_encryption_initial);
+  EXPECT_EQ(transport_.client()->alert(), SSL_AD_UNEXPECTED_MESSAGE);
+
+  // Sanity-check client did get far enough to process the ServerHello and
+  // install keys.
+  EXPECT_TRUE(transport_.client()->HasSecrets(ssl_encryption_handshake));
+}
+
+// Test that |SSL_provide_quic_data| will reject data at the wrong level.
+TEST_F(QUICMethodTest, ProvideWrongLevel) {
+  const SSL_QUIC_METHOD quic_method = {
+      SetEncryptionSecretsCallback,
+      AddHandshakeDataCallback,
+      FlushFlightCallback,
+      SendAlertCallback,
+  };
+
+  ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
+  ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
+  ASSERT_TRUE(CreateClientAndServer());
+
+  // Send the ClientHello and ServerHello through Finished.
+  ASSERT_EQ(SSL_do_handshake(client_.get()), -1);
+  ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_WANT_READ);
+  ASSERT_TRUE(ProvideHandshakeData(server_.get()));
+  ASSERT_EQ(SSL_do_handshake(server_.get()), -1);
+  ASSERT_EQ(SSL_get_error(server_.get(), -1), SSL_ERROR_WANT_READ);
+
+  // The client is still waiting for the ServerHello at initial
+  // encryption.
+  ASSERT_EQ(ssl_encryption_initial, SSL_quic_read_level(client_.get()));
+
+  // Data cannot be provided at the next level.
+  std::vector<uint8_t> data;
+  ASSERT_TRUE(
+      transport_.client()->ReadHandshakeData(&data, ssl_encryption_initial));
+  ASSERT_FALSE(SSL_provide_quic_data(client_.get(), ssl_encryption_handshake,
+                                     data.data(), data.size()));
+  ERR_clear_error();
+
+  // Progress to EncryptedExtensions.
+  ASSERT_TRUE(SSL_provide_quic_data(client_.get(), ssl_encryption_initial,
+                                    data.data(), data.size()));
+  ASSERT_EQ(SSL_do_handshake(client_.get()), -1);
+  ASSERT_EQ(SSL_get_error(client_.get(), -1), SSL_ERROR_WANT_READ);
+  ASSERT_EQ(ssl_encryption_handshake, SSL_quic_read_level(client_.get()));
+
+  // Data cannot be provided at the previous level.
+  ASSERT_TRUE(
+      transport_.client()->ReadHandshakeData(&data, ssl_encryption_handshake));
+  ASSERT_FALSE(SSL_provide_quic_data(client_.get(), ssl_encryption_initial,
+                                     data.data(), data.size()));
+}
+
+TEST_F(QUICMethodTest, TooMuchData) {
+  const SSL_QUIC_METHOD quic_method = {
+      SetEncryptionSecretsCallback,
+      AddHandshakeDataCallback,
+      FlushFlightCallback,
+      SendAlertCallback,
+  };
+
+  ASSERT_TRUE(SSL_CTX_set_quic_method(client_ctx_.get(), &quic_method));
+  ASSERT_TRUE(SSL_CTX_set_quic_method(server_ctx_.get(), &quic_method));
+  ASSERT_TRUE(CreateClientAndServer());
+
+  size_t limit =
+      SSL_quic_max_handshake_flight_len(client_.get(), ssl_encryption_initial);
+  uint8_t b = 0;
+  for (size_t i = 0; i < limit; i++) {
+    ASSERT_TRUE(
+        SSL_provide_quic_data(client_.get(), ssl_encryption_initial, &b, 1));
+  }
+
+  EXPECT_FALSE(
+      SSL_provide_quic_data(client_.get(), ssl_encryption_initial, &b, 1));
+}
+
 // TODO(davidben): Convert this file to GTest properly.
 TEST(SSLTest, AllTests) {
   if (!TestSSL_SESSIONEncoding(kOpenSSLSession) ||
diff --git a/src/ssl/ssl_versions.cc b/src/ssl/ssl_versions.cc
index 911fb7e..7df7fe7 100644
--- a/src/ssl/ssl_versions.cc
+++ b/src/ssl/ssl_versions.cc
@@ -217,6 +217,11 @@
   uint16_t min_version = hs->config->conf_min_version;
   uint16_t max_version = hs->config->conf_max_version;
 
+  // QUIC requires TLS 1.3.
+  if (hs->ssl->ctx->quic_method && min_version < TLS1_3_VERSION) {
+    min_version = TLS1_3_VERSION;
+  }
+
   // OpenSSL's API for controlling versions entails blacklisting individual
   // protocols. This has two problems. First, on the client, the protocol can
   // only express a contiguous range of versions. Second, a library consumer
diff --git a/src/ssl/tls13_both.cc b/src/ssl/tls13_both.cc
index a02d35d..299fc14 100644
--- a/src/ssl/tls13_both.cc
+++ b/src/ssl/tls13_both.cc
@@ -645,7 +645,8 @@
 bool tls13_post_handshake(SSL *ssl, const SSLMessage &msg) {
   if (msg.type == SSL3_MT_KEY_UPDATE) {
     ssl->s3->key_update_count++;
-    if (ssl->s3->key_update_count > kMaxKeyUpdates) {
+    if (ssl->ctx->quic_method != nullptr ||
+        ssl->s3->key_update_count > kMaxKeyUpdates) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MANY_KEY_UPDATES);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
       return false;
diff --git a/src/ssl/tls13_client.cc b/src/ssl/tls13_client.cc
index 26f5fb9..fb56001 100644
--- a/src/ssl/tls13_client.cc
+++ b/src/ssl/tls13_client.cc
@@ -389,18 +389,17 @@
   }
 
   if (!tls13_advance_key_schedule(hs, dhe_secret.data(), dhe_secret.size()) ||
-      !ssl_hash_message(hs, msg) ||
-      !tls13_derive_handshake_secrets(hs) ||
-      !tls13_set_traffic_key(ssl, evp_aead_open, hs->server_handshake_secret,
-                             hs->hash_len)) {
+      !ssl_hash_message(hs, msg) || !tls13_derive_handshake_secrets(hs) ||
+      !tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_open,
+                             hs->server_handshake_secret, hs->hash_len)) {
     return ssl_hs_error;
   }
 
   if (!hs->early_data_offered) {
     // If not sending early data, set client traffic keys now so that alerts are
     // encrypted.
-    if (!tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret,
-                               hs->hash_len)) {
+    if (!tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_seal,
+                               hs->client_handshake_secret, hs->hash_len)) {
       return ssl_hs_error;
     }
   }
@@ -641,8 +640,8 @@
   }
 
   if (hs->early_data_offered) {
-    if (!tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret,
-                               hs->hash_len)) {
+    if (!tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_seal,
+                               hs->client_handshake_secret, hs->hash_len)) {
       return ssl_hs_error;
     }
   }
@@ -736,10 +735,10 @@
   }
 
   // Derive the final keys and enable them.
-  if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->server_traffic_secret_0,
-                             hs->hash_len) ||
-      !tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_traffic_secret_0,
-                             hs->hash_len) ||
+  if (!tls13_set_traffic_key(ssl, ssl_encryption_application, evp_aead_open,
+                             hs->server_traffic_secret_0, hs->hash_len) ||
+      !tls13_set_traffic_key(ssl, ssl_encryption_application, evp_aead_seal,
+                             hs->client_traffic_secret_0, hs->hash_len) ||
       !tls13_derive_resumption_secret(hs)) {
     return ssl_hs_error;
   }
@@ -883,7 +882,8 @@
     session->timeout = server_timeout;
   }
 
-  if (!tls13_derive_session_psk(session.get(), ticket_nonce)) {
+  if (!tls13_derive_session_psk(session.get(), ticket_nonce,
+                                ssl->ctx->quic_method != nullptr)) {
     return false;
   }
 
diff --git a/src/ssl/tls13_enc.cc b/src/ssl/tls13_enc.cc
index 5e1f19a..f18084e 100644
--- a/src/ssl/tls13_enc.cc
+++ b/src/ssl/tls13_enc.cc
@@ -69,19 +69,27 @@
 static bool hkdf_expand_label(uint8_t *out, const EVP_MD *digest,
                               const uint8_t *secret, size_t secret_len,
                               const char *label, size_t label_len,
-                              const uint8_t *hash, size_t hash_len,
-                              size_t len) {
-  static const char kTLS13LabelVersion[] = "tls13 ";
+                              const uint8_t *hash, size_t hash_len, size_t len,
+                              bool use_quic_label) {
+  static const char kTLS13ProtocolLabel[] = "tls13 ";
+  static const char kQUICProtocolLabel[] = "quic ";
+
+  const char *protocol_label;
+  if (use_quic_label) {
+    protocol_label = kQUICProtocolLabel;
+  } else {
+    protocol_label = kTLS13ProtocolLabel;
+  }
 
   ScopedCBB cbb;
   CBB child;
   Array<uint8_t> hkdf_label;
-  if (!CBB_init(cbb.get(), 2 + 1 + strlen(kTLS13LabelVersion) + label_len + 1 +
-                               hash_len) ||
+  if (!CBB_init(cbb.get(),
+                2 + 1 + strlen(protocol_label) + label_len + 1 + hash_len) ||
       !CBB_add_u16(cbb.get(), len) ||
       !CBB_add_u8_length_prefixed(cbb.get(), &child) ||
-      !CBB_add_bytes(&child, (const uint8_t *)kTLS13LabelVersion,
-                     strlen(kTLS13LabelVersion)) ||
+      !CBB_add_bytes(&child, (const uint8_t *)protocol_label,
+                     strlen(protocol_label)) ||
       !CBB_add_bytes(&child, (const uint8_t *)label, label_len) ||
       !CBB_add_u8_length_prefixed(cbb.get(), &child) ||
       !CBB_add_bytes(&child, hash, hash_len) ||
@@ -107,7 +115,8 @@
   if (!hkdf_expand_label(hs->secret, hs->transcript.Digest(), hs->secret,
                          hs->hash_len, kTLS13LabelDerived,
                          strlen(kTLS13LabelDerived), derive_context,
-                         derive_context_len, hs->hash_len)) {
+                         derive_context_len, hs->hash_len,
+                         hs->ssl->ctx->quic_method != nullptr)) {
     return false;
   }
 
@@ -128,10 +137,12 @@
 
   return hkdf_expand_label(out, hs->transcript.Digest(), hs->secret,
                            hs->hash_len, label, label_len, context_hash,
-                           context_hash_len, len);
+                           context_hash_len, len,
+                           hs->ssl->ctx->quic_method != nullptr);
 }
 
-bool tls13_set_traffic_key(SSL *ssl, enum evp_aead_direction_t direction,
+bool tls13_set_traffic_key(SSL *ssl, enum ssl_encryption_level_t level,
+                           enum evp_aead_direction_t direction,
                            const uint8_t *traffic_secret,
                            size_t traffic_secret_len) {
   const SSL_SESSION *session = SSL_get_session(ssl);
@@ -142,36 +153,48 @@
     return false;
   }
 
-  // Look up cipher suite properties.
-  const EVP_AEAD *aead;
-  size_t discard;
-  if (!ssl_cipher_get_evp_aead(&aead, &discard, &discard, session->cipher,
-                               version, SSL_is_dtls(ssl))) {
-    return false;
+  UniquePtr<SSLAEADContext> traffic_aead;
+  if (ssl->ctx->quic_method == nullptr) {
+    // Look up cipher suite properties.
+    const EVP_AEAD *aead;
+    size_t discard;
+    if (!ssl_cipher_get_evp_aead(&aead, &discard, &discard, session->cipher,
+                                 version, SSL_is_dtls(ssl))) {
+      return false;
+    }
+
+    const EVP_MD *digest = ssl_session_get_digest(session);
+
+    // Derive the key.
+    size_t key_len = EVP_AEAD_key_length(aead);
+    uint8_t key[EVP_AEAD_MAX_KEY_LENGTH];
+    if (!hkdf_expand_label(key, digest, traffic_secret, traffic_secret_len,
+                           "key", 3, NULL, 0, key_len,
+                           ssl->ctx->quic_method != nullptr)) {
+      return false;
+    }
+
+    // Derive the IV.
+    size_t iv_len = EVP_AEAD_nonce_length(aead);
+    uint8_t iv[EVP_AEAD_MAX_NONCE_LENGTH];
+    if (!hkdf_expand_label(iv, digest, traffic_secret, traffic_secret_len, "iv",
+                           2, NULL, 0, iv_len,
+                           ssl->ctx->quic_method != nullptr)) {
+      return false;
+    }
+
+
+    traffic_aead = SSLAEADContext::Create(
+        direction, session->ssl_version, SSL_is_dtls(ssl), session->cipher,
+        MakeConstSpan(key, key_len), Span<const uint8_t>(),
+        MakeConstSpan(iv, iv_len));
+  } else {
+    // Install a placeholder SSLAEADContext so that SSL accessors work. The
+    // encryption itself will be handled by the SSL_QUIC_METHOD.
+    traffic_aead =
+        SSLAEADContext::CreatePlaceholderForQUIC(version, session->cipher);
   }
 
-  const EVP_MD *digest = ssl_session_get_digest(session);
-
-  // Derive the key.
-  size_t key_len = EVP_AEAD_key_length(aead);
-  uint8_t key[EVP_AEAD_MAX_KEY_LENGTH];
-  if (!hkdf_expand_label(key, digest, traffic_secret, traffic_secret_len, "key",
-                         3, NULL, 0, key_len)) {
-    return false;
-  }
-
-  // Derive the IV.
-  size_t iv_len = EVP_AEAD_nonce_length(aead);
-  uint8_t iv[EVP_AEAD_MAX_NONCE_LENGTH];
-  if (!hkdf_expand_label(iv, digest, traffic_secret, traffic_secret_len, "iv",
-                         2, NULL, 0, iv_len)) {
-    return false;
-  }
-
-  UniquePtr<SSLAEADContext> traffic_aead =
-      SSLAEADContext::Create(direction, session->ssl_version, SSL_is_dtls(ssl),
-                             session->cipher, MakeConstSpan(key, key_len),
-                             Span<const uint8_t>(), MakeConstSpan(iv, iv_len));
   if (!traffic_aead) {
     return false;
   }
@@ -191,10 +214,12 @@
     OPENSSL_memmove(ssl->s3->read_traffic_secret, traffic_secret,
                     traffic_secret_len);
     ssl->s3->read_traffic_secret_len = traffic_secret_len;
+    ssl->s3->read_level = level;
   } else {
     OPENSSL_memmove(ssl->s3->write_traffic_secret, traffic_secret,
                     traffic_secret_len);
     ssl->s3->write_traffic_secret_len = traffic_secret_len;
+    ssl->s3->write_level = level;
   }
 
   return true;
@@ -223,40 +248,103 @@
     return false;
   }
   ssl->s3->early_exporter_secret_len = hs->hash_len;
+
+  if (ssl->ctx->quic_method != nullptr) {
+    if (ssl->server) {
+      if (!ssl->ctx->quic_method->set_encryption_secrets(
+              ssl, ssl_encryption_early_data, nullptr, hs->early_traffic_secret,
+              hs->hash_len)) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR);
+        return false;
+      }
+    } else {
+      if (!ssl->ctx->quic_method->set_encryption_secrets(
+              ssl, ssl_encryption_early_data, hs->early_traffic_secret, nullptr,
+              hs->hash_len)) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR);
+        return false;
+      }
+    }
+  }
+
   return true;
 }
 
 bool tls13_derive_handshake_secrets(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-  return derive_secret(hs, hs->client_handshake_secret, hs->hash_len,
-                       kTLS13LabelClientHandshakeTraffic,
-                       strlen(kTLS13LabelClientHandshakeTraffic)) &&
-         ssl_log_secret(ssl, "CLIENT_HANDSHAKE_TRAFFIC_SECRET",
-                        hs->client_handshake_secret, hs->hash_len) &&
-         derive_secret(hs, hs->server_handshake_secret, hs->hash_len,
-                       kTLS13LabelServerHandshakeTraffic,
-                       strlen(kTLS13LabelServerHandshakeTraffic)) &&
-         ssl_log_secret(ssl, "SERVER_HANDSHAKE_TRAFFIC_SECRET",
-                        hs->server_handshake_secret, hs->hash_len);
+  if (!derive_secret(hs, hs->client_handshake_secret, hs->hash_len,
+                     kTLS13LabelClientHandshakeTraffic,
+                     strlen(kTLS13LabelClientHandshakeTraffic)) ||
+      !ssl_log_secret(ssl, "CLIENT_HANDSHAKE_TRAFFIC_SECRET",
+                      hs->client_handshake_secret, hs->hash_len) ||
+      !derive_secret(hs, hs->server_handshake_secret, hs->hash_len,
+                     kTLS13LabelServerHandshakeTraffic,
+                     strlen(kTLS13LabelServerHandshakeTraffic)) ||
+      !ssl_log_secret(ssl, "SERVER_HANDSHAKE_TRAFFIC_SECRET",
+                      hs->server_handshake_secret, hs->hash_len)) {
+    return false;
+  }
+
+  if (ssl->ctx->quic_method != nullptr) {
+    if (ssl->server) {
+      if (!ssl->ctx->quic_method->set_encryption_secrets(
+              ssl, ssl_encryption_handshake, hs->client_handshake_secret,
+              hs->server_handshake_secret, hs->hash_len)) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR);
+        return false;
+      }
+    } else {
+      if (!ssl->ctx->quic_method->set_encryption_secrets(
+              ssl, ssl_encryption_handshake, hs->server_handshake_secret,
+              hs->client_handshake_secret, hs->hash_len)) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR);
+        return false;
+      }
+    }
+  }
+
+  return true;
 }
 
 bool tls13_derive_application_secrets(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   ssl->s3->exporter_secret_len = hs->hash_len;
-  return derive_secret(hs, hs->client_traffic_secret_0, hs->hash_len,
-                       kTLS13LabelClientApplicationTraffic,
-                       strlen(kTLS13LabelClientApplicationTraffic)) &&
-         ssl_log_secret(ssl, "CLIENT_TRAFFIC_SECRET_0",
-                        hs->client_traffic_secret_0, hs->hash_len) &&
-         derive_secret(hs, hs->server_traffic_secret_0, hs->hash_len,
-                       kTLS13LabelServerApplicationTraffic,
-                       strlen(kTLS13LabelServerApplicationTraffic)) &&
-         ssl_log_secret(ssl, "SERVER_TRAFFIC_SECRET_0",
-                        hs->server_traffic_secret_0, hs->hash_len) &&
-         derive_secret(hs, ssl->s3->exporter_secret, hs->hash_len,
-                       kTLS13LabelExporter, strlen(kTLS13LabelExporter)) &&
-         ssl_log_secret(ssl, "EXPORTER_SECRET", ssl->s3->exporter_secret,
-                        hs->hash_len);
+  if (!derive_secret(hs, hs->client_traffic_secret_0, hs->hash_len,
+                     kTLS13LabelClientApplicationTraffic,
+                     strlen(kTLS13LabelClientApplicationTraffic)) ||
+      !ssl_log_secret(ssl, "CLIENT_TRAFFIC_SECRET_0",
+                      hs->client_traffic_secret_0, hs->hash_len) ||
+      !derive_secret(hs, hs->server_traffic_secret_0, hs->hash_len,
+                     kTLS13LabelServerApplicationTraffic,
+                     strlen(kTLS13LabelServerApplicationTraffic)) ||
+      !ssl_log_secret(ssl, "SERVER_TRAFFIC_SECRET_0",
+                      hs->server_traffic_secret_0, hs->hash_len) ||
+      !derive_secret(hs, ssl->s3->exporter_secret, hs->hash_len,
+                     kTLS13LabelExporter, strlen(kTLS13LabelExporter)) ||
+      !ssl_log_secret(ssl, "EXPORTER_SECRET", ssl->s3->exporter_secret,
+                      hs->hash_len)) {
+    return false;
+  }
+
+  if (ssl->ctx->quic_method != nullptr) {
+    if (ssl->server) {
+      if (!ssl->ctx->quic_method->set_encryption_secrets(
+              ssl, ssl_encryption_application, hs->client_traffic_secret_0,
+              hs->server_traffic_secret_0, hs->hash_len)) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR);
+        return false;
+      }
+    } else {
+      if (!ssl->ctx->quic_method->set_encryption_secrets(
+              ssl, ssl_encryption_application, hs->server_traffic_secret_0,
+              hs->client_traffic_secret_0, hs->hash_len)) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_QUIC_INTERNAL_ERROR);
+        return false;
+      }
+    }
+  }
+
+  return true;
 }
 
 static const char kTLS13LabelApplicationTraffic[] = "traffic upd";
@@ -273,13 +361,15 @@
   }
 
   const EVP_MD *digest = ssl_session_get_digest(SSL_get_session(ssl));
-  if (!hkdf_expand_label(
-          secret, digest, secret, secret_len, kTLS13LabelApplicationTraffic,
-          strlen(kTLS13LabelApplicationTraffic), NULL, 0, secret_len)) {
+  if (!hkdf_expand_label(secret, digest, secret, secret_len,
+                         kTLS13LabelApplicationTraffic,
+                         strlen(kTLS13LabelApplicationTraffic), NULL, 0,
+                         secret_len, ssl->ctx->quic_method != nullptr)) {
     return false;
   }
 
-  return tls13_set_traffic_key(ssl, direction, secret, secret_len);
+  return tls13_set_traffic_key(ssl, ssl_encryption_application, direction,
+                               secret, secret_len);
 }
 
 static const char kTLS13LabelResumption[] = "res master";
@@ -302,11 +392,13 @@
 static bool tls13_verify_data(const EVP_MD *digest, uint16_t version,
                               uint8_t *out, size_t *out_len,
                               const uint8_t *secret, size_t hash_len,
-                              uint8_t *context, size_t context_len) {
+                              uint8_t *context, size_t context_len,
+                              bool use_quic) {
   uint8_t key[EVP_MAX_MD_SIZE];
   unsigned len;
   if (!hkdf_expand_label(key, digest, secret, hash_len, kTLS13LabelFinished,
-                         strlen(kTLS13LabelFinished), NULL, 0, hash_len) ||
+                         strlen(kTLS13LabelFinished), NULL, 0, hash_len,
+                         use_quic) ||
       HMAC(digest, key, hash_len, context, context_len, out, &len) == NULL) {
     return false;
   }
@@ -328,7 +420,8 @@
   if (!hs->transcript.GetHash(context_hash, &context_hash_len) ||
       !tls13_verify_data(hs->transcript.Digest(), hs->ssl->version, out,
                          out_len, traffic_secret, hs->hash_len, context_hash,
-                         context_hash_len)) {
+                         context_hash_len,
+                         hs->ssl->ctx->quic_method != nullptr)) {
     return 0;
   }
   return 1;
@@ -336,12 +429,13 @@
 
 static const char kTLS13LabelResumptionPSK[] = "resumption";
 
-bool tls13_derive_session_psk(SSL_SESSION *session, Span<const uint8_t> nonce) {
+bool tls13_derive_session_psk(SSL_SESSION *session, Span<const uint8_t> nonce,
+                              bool use_quic) {
   const EVP_MD *digest = ssl_session_get_digest(session);
   return hkdf_expand_label(session->master_key, digest, session->master_key,
                            session->master_key_length, kTLS13LabelResumptionPSK,
                            strlen(kTLS13LabelResumptionPSK), nonce.data(),
-                           nonce.size(), session->master_key_length);
+                           nonce.size(), session->master_key_length, use_quic);
 }
 
 static const char kTLS13LabelExportKeying[] = "exporter";
@@ -370,11 +464,12 @@
                     nullptr) &&
          hkdf_expand_label(derived_secret, digest, secret.data(), secret.size(),
                            label.data(), label.size(), export_context,
-                           export_context_len, derived_secret_len) &&
+                           export_context_len, derived_secret_len,
+                           ssl->ctx->quic_method != nullptr) &&
          hkdf_expand_label(out.data(), digest, derived_secret,
                            derived_secret_len, kTLS13LabelExportKeying,
                            strlen(kTLS13LabelExportKeying), hash, hash_len,
-                           out.size());
+                           out.size(), ssl->ctx->quic_method != nullptr);
 }
 
 static const char kTLS13LabelPSKBinder[] = "res binder";
@@ -382,7 +477,7 @@
 static bool tls13_psk_binder(uint8_t *out, uint16_t version,
                              const EVP_MD *digest, uint8_t *psk, size_t psk_len,
                              uint8_t *context, size_t context_len,
-                             size_t hash_len) {
+                             size_t hash_len, bool use_quic) {
   uint8_t binder_context[EVP_MAX_MD_SIZE];
   unsigned binder_context_len;
   if (!EVP_Digest(NULL, 0, binder_context, &binder_context_len, digest, NULL)) {
@@ -400,9 +495,10 @@
   size_t len;
   if (!hkdf_expand_label(binder_key, digest, early_secret, hash_len,
                          kTLS13LabelPSKBinder, strlen(kTLS13LabelPSKBinder),
-                         binder_context, binder_context_len, hash_len) ||
+                         binder_context, binder_context_len, hash_len,
+                         use_quic) ||
       !tls13_verify_data(digest, version, out, &len, binder_key, hash_len,
-                         context, context_len)) {
+                         context, context_len, use_quic)) {
     return false;
   }
 
@@ -435,7 +531,7 @@
   if (!tls13_psk_binder(verify_data, ssl->session->ssl_version, digest,
                         ssl->session->master_key,
                         ssl->session->master_key_length, context, context_len,
-                        hash_len)) {
+                        hash_len, ssl->ctx->quic_method != nullptr)) {
     return false;
   }
 
@@ -467,16 +563,16 @@
   CBS binder;
   if (!tls13_psk_binder(verify_data, hs->ssl->version, hs->transcript.Digest(),
                         session->master_key, session->master_key_length,
-                        context, context_len, hash_len) ||
+                        context, context_len, hash_len,
+                        hs->ssl->ctx->quic_method != nullptr) ||
       // We only consider the first PSK, so compare against the first binder.
       !CBS_get_u8_length_prefixed(binders, &binder)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return false;
   }
 
-  bool binder_ok =
-      CBS_len(&binder) == hash_len &&
-      CRYPTO_memcmp(CBS_data(&binder), verify_data, hash_len) == 0;
+  bool binder_ok = CBS_len(&binder) == hash_len &&
+                   CRYPTO_memcmp(CBS_data(&binder), verify_data, hash_len) == 0;
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
   binder_ok = true;
 #endif
diff --git a/src/ssl/tls13_server.cc b/src/ssl/tls13_server.cc
index 0d82d68..969d4b1 100644
--- a/src/ssl/tls13_server.cc
+++ b/src/ssl/tls13_server.cc
@@ -195,7 +195,8 @@
         !CBB_add_u8_length_prefixed(&body, &nonce_cbb) ||
         !CBB_add_bytes(&nonce_cbb, nonce, sizeof(nonce)) ||
         !CBB_add_u16_length_prefixed(&body, &ticket) ||
-        !tls13_derive_session_psk(session.get(), nonce) ||
+        !tls13_derive_session_psk(session.get(), nonce,
+                                  ssl->ctx->quic_method != nullptr) ||
         !ssl_encrypt_ticket(hs, &ticket, session.get()) ||
         !CBB_add_u16_length_prefixed(&body, &extensions)) {
       return false;
@@ -586,8 +587,8 @@
 
   // Derive and enable the handshake traffic secrets.
   if (!tls13_derive_handshake_secrets(hs) ||
-      !tls13_set_traffic_key(ssl, evp_aead_seal, hs->server_handshake_secret,
-                             hs->hash_len)) {
+      !tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_seal,
+                             hs->server_handshake_secret, hs->hash_len)) {
     return ssl_hs_error;
   }
 
@@ -697,8 +698,8 @@
       // Update the secret to the master secret and derive traffic keys.
       !tls13_advance_key_schedule(hs, kZeroes, hs->hash_len) ||
       !tls13_derive_application_secrets(hs) ||
-      !tls13_set_traffic_key(ssl, evp_aead_seal, hs->server_traffic_secret_0,
-                             hs->hash_len)) {
+      !tls13_set_traffic_key(ssl, ssl_encryption_application, evp_aead_seal,
+                             hs->server_traffic_secret_0, hs->hash_len)) {
     return ssl_hs_error;
   }
 
@@ -750,8 +751,8 @@
 static enum ssl_hs_wait_t do_read_second_client_flight(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   if (ssl->s3->early_data_accepted) {
-    if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->early_traffic_secret,
-                               hs->hash_len)) {
+    if (!tls13_set_traffic_key(ssl, ssl_encryption_early_data, evp_aead_open,
+                               hs->early_traffic_secret, hs->hash_len)) {
       return ssl_hs_error;
     }
     hs->can_early_write = true;
@@ -785,8 +786,8 @@
       ssl->method->next_message(ssl);
     }
   }
-  if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->client_handshake_secret,
-                             hs->hash_len)) {
+  if (!tls13_set_traffic_key(ssl, ssl_encryption_handshake, evp_aead_open,
+                             hs->client_handshake_secret, hs->hash_len)) {
     return ssl_hs_error;
   }
   hs->tls13_state = ssl->s3->early_data_accepted
@@ -892,8 +893,8 @@
       // and derived the resumption secret.
       !tls13_process_finished(hs, msg, ssl->s3->early_data_accepted) ||
       // evp_aead_seal keys have already been switched.
-      !tls13_set_traffic_key(ssl, evp_aead_open, hs->client_traffic_secret_0,
-                             hs->hash_len)) {
+      !tls13_set_traffic_key(ssl, ssl_encryption_application, evp_aead_open,
+                             hs->client_traffic_secret_0, hs->hash_len)) {
     return ssl_hs_error;
   }