external/boringssl: Sync to bbfe603519bc54fbc4c8dd87efe1ed385df550b4. am: 6d0d00e090 am: 9ef60e37fc
am: d922d6aa22

Change-Id: I50dd83aaa70fdd2abd93068cb0dd64789109992d
diff --git a/BORINGSSL_REVISION b/BORINGSSL_REVISION
index 4f7aa82..4d70d6c 100644
--- a/BORINGSSL_REVISION
+++ b/BORINGSSL_REVISION
@@ -1 +1 @@
-2d05568a7b7bc62affbd13ea97a81b5829b99794
+bbfe603519bc54fbc4c8dd87efe1ed385df550b4
diff --git a/err_data.c b/err_data.c
index 87a88dd..ea6dd73 100644
--- a/err_data.c
+++ b/err_data.c
@@ -74,51 +74,51 @@
     0xc3a8845,
     0xc3b00ea,
     0x10320845,
-    0x103293c7,
-    0x103313d3,
-    0x103393ec,
-    0x103413ff,
-    0x10348ea7,
-    0x10350c35,
-    0x10359412,
-    0x10361427,
-    0x1036943a,
-    0x10371459,
-    0x10379472,
-    0x10381487,
-    0x103894a5,
-    0x103914b4,
-    0x103994d0,
-    0x103a14eb,
-    0x103a94fa,
-    0x103b1516,
-    0x103b9531,
-    0x103c1548,
+    0x1032943b,
+    0x10331447,
+    0x10339460,
+    0x10341473,
+    0x10348eb4,
+    0x10350c42,
+    0x10359486,
+    0x1036149b,
+    0x103694ae,
+    0x103714cd,
+    0x103794e6,
+    0x103814fb,
+    0x10389519,
+    0x10391528,
+    0x10399544,
+    0x103a155f,
+    0x103a956e,
+    0x103b158a,
+    0x103b95a5,
+    0x103c15bc,
     0x103c80ea,
-    0x103d1559,
-    0x103d956d,
-    0x103e158c,
-    0x103e959b,
-    0x103f15b2,
-    0x103f95c5,
+    0x103d15cd,
+    0x103d95e1,
+    0x103e1600,
+    0x103e960f,
+    0x103f1626,
+    0x103f9639,
     0x10400c06,
-    0x104095d8,
-    0x104115f6,
-    0x10419609,
-    0x10421623,
-    0x10429633,
-    0x10431647,
-    0x1043965d,
-    0x10441675,
-    0x1044968a,
-    0x1045169e,
-    0x104596b0,
+    0x1040964c,
+    0x1041166a,
+    0x1041967d,
+    0x10421697,
+    0x104296a7,
+    0x104316bb,
+    0x104396d1,
+    0x104416e9,
+    0x104496fe,
+    0x10451712,
+    0x10459724,
     0x104605fb,
     0x1046893f,
-    0x104716c5,
-    0x104796dc,
-    0x104816f1,
-    0x104896ff,
+    0x10471739,
+    0x10479750,
+    0x10481765,
+    0x10489773,
     0x14320be9,
     0x14328bf7,
     0x14330c06,
@@ -126,94 +126,94 @@
     0x143400ac,
     0x143480ea,
     0x18320083,
-    0x18328efd,
+    0x18328f0a,
     0x183300ac,
-    0x18338f13,
-    0x18340f27,
+    0x18338f20,
+    0x18340f34,
     0x183480ea,
-    0x18350f3c,
-    0x18358f54,
-    0x18360f69,
-    0x18368f7d,
-    0x18370fa1,
-    0x18378fb7,
-    0x18380fcb,
-    0x18388fdb,
+    0x18350f49,
+    0x18358f61,
+    0x18360f76,
+    0x18368f8a,
+    0x18370fae,
+    0x18378fc4,
+    0x18380fd8,
+    0x18388fe8,
     0x18390a57,
-    0x18398feb,
-    0x183a1000,
-    0x183a9014,
-    0x183b0c41,
-    0x183b9021,
-    0x183c1033,
-    0x183c903e,
-    0x183d104e,
-    0x183d905f,
-    0x183e1070,
-    0x183e9082,
-    0x183f10ab,
-    0x183f90c4,
-    0x184010dc,
+    0x18398ff8,
+    0x183a100d,
+    0x183a9021,
+    0x183b0c4e,
+    0x183b902e,
+    0x183c1040,
+    0x183c904b,
+    0x183d105b,
+    0x183d906c,
+    0x183e107d,
+    0x183e908f,
+    0x183f10b8,
+    0x183f90d1,
+    0x184010e9,
     0x184086d3,
-    0x20321103,
-    0x2432110f,
+    0x20321110,
+    0x2432111c,
     0x24328985,
-    0x24331121,
-    0x2433912e,
-    0x2434113b,
-    0x2434914d,
-    0x2435115c,
-    0x24359179,
-    0x24361186,
-    0x24369194,
-    0x243711a2,
-    0x243791b0,
-    0x243811b9,
-    0x243891c6,
-    0x243911d9,
-    0x28320c29,
-    0x28328c41,
+    0x2433112e,
+    0x2433913b,
+    0x24341148,
+    0x2434915a,
+    0x24351169,
+    0x24359186,
+    0x24361193,
+    0x243691a1,
+    0x243711af,
+    0x243791bd,
+    0x243811c6,
+    0x243891d3,
+    0x243911e6,
+    0x28320c36,
+    0x28328c4e,
     0x28330c06,
-    0x28338c54,
-    0x28340c35,
+    0x28338c61,
+    0x28340c42,
     0x283480ac,
     0x283500ea,
-    0x2c322a2c,
-    0x2c32aa3a,
-    0x2c332a4c,
-    0x2c33aa5e,
-    0x2c342a72,
-    0x2c34aa84,
-    0x2c352a9f,
-    0x2c35aab1,
-    0x2c362ac4,
+    0x2c322af1,
+    0x2c32aaff,
+    0x2c332b11,
+    0x2c33ab23,
+    0x2c342b37,
+    0x2c34ab49,
+    0x2c352b64,
+    0x2c35ab76,
+    0x2c362b89,
     0x2c36832d,
-    0x2c372ad1,
-    0x2c37aae3,
-    0x2c382af6,
-    0x2c38ab0d,
-    0x2c392b1b,
-    0x2c39ab2b,
-    0x2c3a2b3d,
-    0x2c3aab51,
-    0x2c3b2b62,
-    0x2c3bab81,
-    0x2c3c2b95,
-    0x2c3cabab,
-    0x2c3d2bc4,
-    0x2c3dabe1,
-    0x2c3e2bf2,
-    0x2c3eac00,
-    0x2c3f2c18,
-    0x2c3fac30,
-    0x2c402c3d,
-    0x2c409103,
-    0x2c412c4e,
-    0x2c41ac61,
-    0x2c4210dc,
-    0x2c42ac72,
+    0x2c372b96,
+    0x2c37aba8,
+    0x2c382bbb,
+    0x2c38abd2,
+    0x2c392be0,
+    0x2c39abf0,
+    0x2c3a2c02,
+    0x2c3aac16,
+    0x2c3b2c27,
+    0x2c3bac46,
+    0x2c3c2c5a,
+    0x2c3cac70,
+    0x2c3d2c89,
+    0x2c3daca6,
+    0x2c3e2cb7,
+    0x2c3eacc5,
+    0x2c3f2cdd,
+    0x2c3facf5,
+    0x2c402d02,
+    0x2c409110,
+    0x2c412d13,
+    0x2c41ad26,
+    0x2c4210e9,
+    0x2c42ad37,
     0x2c430720,
-    0x2c43ab73,
+    0x2c43ac38,
     0x30320000,
     0x30328015,
     0x3033001f,
@@ -314,245 +314,248 @@
     0x34348bd3,
     0x34350bb7,
     0x3c320083,
-    0x3c328c7e,
-    0x3c330c97,
-    0x3c338cb2,
-    0x3c340ccf,
-    0x3c348cf9,
-    0x3c350d14,
-    0x3c358d3a,
-    0x3c360d53,
-    0x3c368d6b,
-    0x3c370d7c,
-    0x3c378d8a,
-    0x3c380d97,
-    0x3c388dab,
-    0x3c390c41,
-    0x3c398dbf,
-    0x3c3a0dd3,
+    0x3c328c8b,
+    0x3c330ca4,
+    0x3c338cbf,
+    0x3c340cdc,
+    0x3c348d06,
+    0x3c350d21,
+    0x3c358d47,
+    0x3c360d60,
+    0x3c368d78,
+    0x3c370d89,
+    0x3c378d97,
+    0x3c380da4,
+    0x3c388db8,
+    0x3c390c4e,
+    0x3c398dcc,
+    0x3c3a0de0,
     0x3c3a88ff,
-    0x3c3b0de3,
-    0x3c3b8dfe,
-    0x3c3c0e10,
-    0x3c3c8e26,
-    0x3c3d0e30,
-    0x3c3d8e44,
-    0x3c3e0e52,
-    0x3c3e8e77,
-    0x3c3f0c6a,
-    0x3c3f8e60,
+    0x3c3b0df0,
+    0x3c3b8e0b,
+    0x3c3c0e1d,
+    0x3c3c8e33,
+    0x3c3d0e3d,
+    0x3c3d8e51,
+    0x3c3e0e5f,
+    0x3c3e8e84,
+    0x3c3f0c77,
+    0x3c3f8e6d,
     0x3c4000ac,
     0x3c4080ea,
-    0x3c410cea,
-    0x3c418d29,
-    0x40321716,
-    0x4032972c,
-    0x4033175a,
-    0x40339764,
-    0x4034177b,
-    0x40349799,
-    0x403517a9,
-    0x403597bb,
-    0x403617c8,
-    0x403697d4,
-    0x403717e9,
-    0x403797fb,
-    0x40381806,
-    0x40389818,
-    0x40390ea7,
-    0x40399828,
-    0x403a183b,
-    0x403a985c,
-    0x403b186d,
-    0x403b987d,
+    0x3c410cf7,
+    0x3c418d36,
+    0x403217a6,
+    0x403297bc,
+    0x403317ea,
+    0x403397f4,
+    0x4034180b,
+    0x40349829,
+    0x40351839,
+    0x4035984b,
+    0x40361858,
+    0x40369864,
+    0x40371879,
+    0x4037988b,
+    0x40381896,
+    0x403898a8,
+    0x40390eb4,
+    0x403998b8,
+    0x403a18cb,
+    0x403a98ec,
+    0x403b18fd,
+    0x403b990d,
     0x403c0064,
     0x403c8083,
-    0x403d1901,
-    0x403d9917,
-    0x403e1926,
-    0x403e995e,
-    0x403f1978,
-    0x403f9986,
-    0x4040199b,
-    0x404099af,
-    0x404119cc,
-    0x404199e7,
-    0x40421a00,
-    0x40429a13,
-    0x40431a27,
-    0x40439a3f,
-    0x40441a56,
+    0x403d1991,
+    0x403d99a7,
+    0x403e19b6,
+    0x403e99ee,
+    0x403f1a08,
+    0x403f9a16,
+    0x40401a2b,
+    0x40409a58,
+    0x40411a75,
+    0x40419a90,
+    0x40421aa9,
+    0x40429abc,
+    0x40431ad0,
+    0x40439ae8,
+    0x40441aff,
     0x404480ac,
-    0x40451a6b,
-    0x40459a7d,
-    0x40461aa1,
-    0x40469ac1,
-    0x40471acf,
-    0x40479af6,
-    0x40481b33,
-    0x40489b4c,
-    0x40491b63,
-    0x40499b7d,
-    0x404a1b94,
-    0x404a9bb2,
-    0x404b1bca,
-    0x404b9be1,
-    0x404c1bf7,
-    0x404c9c09,
-    0x404d1c2a,
-    0x404d9c4c,
-    0x404e1c60,
-    0x404e9c6d,
-    0x404f1c9a,
-    0x404f9cc3,
-    0x40501cfe,
-    0x40509d12,
-    0x40511d2d,
-    0x40521d3d,
-    0x40529d61,
-    0x40531d79,
-    0x40539d8c,
-    0x40541da1,
-    0x40549dc4,
-    0x40551dd2,
-    0x40559def,
-    0x40561dfc,
-    0x40569e15,
-    0x40571e2d,
-    0x40579e40,
-    0x40581e55,
-    0x40589e7c,
-    0x40591eab,
-    0x40599ed8,
-    0x405a1eec,
-    0x405a9efc,
-    0x405b1f14,
-    0x405b9f25,
-    0x405c1f38,
-    0x405c9f59,
-    0x405d1f66,
-    0x405d9f7d,
-    0x405e1fbb,
+    0x40451b14,
+    0x40459b26,
+    0x40461b4a,
+    0x40469b6a,
+    0x40471b78,
+    0x40479b9f,
+    0x40481bdc,
+    0x40489bf5,
+    0x40491c0c,
+    0x40499c26,
+    0x404a1c3d,
+    0x404a9c5b,
+    0x404b1c73,
+    0x404b9c8a,
+    0x404c1ca0,
+    0x404c9cb2,
+    0x404d1cd3,
+    0x404d9cf5,
+    0x404e1d09,
+    0x404e9d16,
+    0x404f1d43,
+    0x404f9d6c,
+    0x40501da7,
+    0x40509dbb,
+    0x40511dd6,
+    0x40521de6,
+    0x40529e0a,
+    0x40531e22,
+    0x40539e35,
+    0x40541e4a,
+    0x40549e6d,
+    0x40551e7b,
+    0x40559e98,
+    0x40561ea5,
+    0x40569ebe,
+    0x40571ed6,
+    0x40579ee9,
+    0x40581efe,
+    0x40589f25,
+    0x40591f54,
+    0x40599f81,
+    0x405a1f95,
+    0x405a9fa5,
+    0x405b1fbd,
+    0x405b9fce,
+    0x405c1fe1,
+    0x405ca002,
+    0x405d200f,
+    0x405da026,
+    0x405e2064,
     0x405e8a95,
-    0x405f1fdc,
-    0x405f9fe9,
-    0x40601ff7,
-    0x4060a019,
-    0x4061205d,
-    0x4061a095,
-    0x406220ac,
-    0x4062a0bd,
-    0x406320ce,
-    0x4063a0e3,
-    0x406420fa,
-    0x4064a126,
-    0x40652141,
-    0x4065a158,
-    0x40662170,
-    0x4066a19a,
-    0x406721c5,
-    0x4067a1e6,
-    0x4068220d,
-    0x4068a22e,
-    0x40692260,
-    0x4069a28e,
-    0x406a22af,
-    0x406aa2cf,
-    0x406b2457,
-    0x406ba47a,
-    0x406c2490,
-    0x406ca70b,
-    0x406d273a,
-    0x406da762,
-    0x406e2790,
-    0x406ea7c4,
-    0x406f27e3,
-    0x406fa7f8,
-    0x4070280b,
-    0x4070a828,
+    0x405f2085,
+    0x405fa092,
+    0x406020a0,
+    0x4060a0c2,
+    0x40612106,
+    0x4061a13e,
+    0x40622155,
+    0x4062a166,
+    0x40632177,
+    0x4063a18c,
+    0x406421a3,
+    0x4064a1cf,
+    0x406521ea,
+    0x4065a201,
+    0x40662219,
+    0x4066a243,
+    0x4067226e,
+    0x4067a28f,
+    0x406822b6,
+    0x4068a2d7,
+    0x40692309,
+    0x4069a337,
+    0x406a2358,
+    0x406aa378,
+    0x406b2500,
+    0x406ba523,
+    0x406c2539,
+    0x406ca7b4,
+    0x406d27e3,
+    0x406da80b,
+    0x406e2839,
+    0x406ea86d,
+    0x406f288c,
+    0x406fa8a1,
+    0x407028b4,
+    0x4070a8d1,
     0x40710800,
-    0x4071a83a,
-    0x4072284d,
-    0x4072a866,
-    0x4073287e,
-    0x40739389,
-    0x40742892,
-    0x4074a8ac,
-    0x407528bd,
-    0x4075a8d1,
-    0x407628df,
-    0x407691c6,
-    0x40772904,
-    0x4077a926,
-    0x40782941,
-    0x4078a97a,
-    0x40792991,
-    0x4079a9a7,
-    0x407a29b3,
-    0x407aa9c6,
-    0x407b29db,
-    0x407ba9ed,
-    0x407c2a02,
-    0x407caa0b,
-    0x407d2249,
-    0x407d9cd3,
-    0x407e2956,
-    0x407e9e8c,
-    0x407f1ae3,
-    0x407f98a3,
-    0x40801caa,
-    0x40809b0b,
-    0x40811d4f,
-    0x40819c84,
-    0x4082277b,
-    0x40829889,
-    0x40831e67,
-    0x4083a10b,
-    0x40841b1f,
-    0x40849ec4,
-    0x40851f49,
-    0x4085a041,
-    0x40861f9d,
-    0x40869ced,
-    0x408727a8,
-    0x4087a072,
-    0x408818ea,
-    0x4088a1f9,
-    0x40891939,
-    0x408998c6,
-    0x408a24b0,
-    0x41f42382,
-    0x41f92414,
-    0x41fe2307,
-    0x41fea4fc,
-    0x41ff25ed,
-    0x4203239b,
-    0x420823bd,
-    0x4208a3f9,
-    0x420922eb,
-    0x4209a433,
-    0x420a2342,
-    0x420aa322,
-    0x420b2362,
-    0x420ba3db,
-    0x420c2609,
-    0x420ca4c9,
-    0x420d24e3,
-    0x420da51a,
-    0x42122534,
-    0x421725d0,
-    0x4217a576,
-    0x421c2598,
-    0x421f2553,
-    0x42212620,
-    0x422625b3,
-    0x422b26ef,
-    0x422ba69d,
-    0x422c26d7,
-    0x422ca65c,
-    0x422d263b,
-    0x422da6bc,
-    0x422e2682,
+    0x4071a8e3,
+    0x407228f6,
+    0x4072a90f,
+    0x40732927,
+    0x407393aa,
+    0x4074293b,
+    0x4074a955,
+    0x40752966,
+    0x4075a97a,
+    0x40762988,
+    0x407691d3,
+    0x407729ad,
+    0x4077a9cf,
+    0x407829ea,
+    0x4078aa23,
+    0x40792a3a,
+    0x4079aa50,
+    0x407a2a5c,
+    0x407aaa6f,
+    0x407b2a84,
+    0x407baa96,
+    0x407c2ac7,
+    0x407caad0,
+    0x407d22f2,
+    0x407d9d7c,
+    0x407e29ff,
+    0x407e9f35,
+    0x407f1b8c,
+    0x407f9933,
+    0x40801d53,
+    0x40809bb4,
+    0x40811df8,
+    0x40819d2d,
+    0x40822824,
+    0x40829919,
+    0x40831f10,
+    0x4083a1b4,
+    0x40841bc8,
+    0x40849f6d,
+    0x40851ff2,
+    0x4085a0ea,
+    0x40862046,
+    0x40869d96,
+    0x40872851,
+    0x4087a11b,
+    0x4088197a,
+    0x4088a2a2,
+    0x408919c9,
+    0x40899956,
+    0x408a2559,
+    0x408a978a,
+    0x408b2aab,
+    0x408b9a3f,
+    0x41f4242b,
+    0x41f924bd,
+    0x41fe23b0,
+    0x41fea5a5,
+    0x41ff2696,
+    0x42032444,
+    0x42082466,
+    0x4208a4a2,
+    0x42092394,
+    0x4209a4dc,
+    0x420a23eb,
+    0x420aa3cb,
+    0x420b240b,
+    0x420ba484,
+    0x420c26b2,
+    0x420ca572,
+    0x420d258c,
+    0x420da5c3,
+    0x421225dd,
+    0x42172679,
+    0x4217a61f,
+    0x421c2641,
+    0x421f25fc,
+    0x422126c9,
+    0x4226265c,
+    0x422b2798,
+    0x422ba746,
+    0x422c2780,
+    0x422ca705,
+    0x422d26e4,
+    0x422da765,
+    0x422e272b,
     0x4432072b,
     0x4432873a,
     0x44330746,
@@ -570,105 +573,113 @@
     0x44390800,
     0x4439880e,
     0x443a0821,
-    0x4c3211f0,
-    0x4c329200,
-    0x4c331213,
-    0x4c339233,
+    0x4c321211,
+    0x4c329221,
+    0x4c331234,
+    0x4c339254,
     0x4c3400ac,
     0x4c3480ea,
-    0x4c35123f,
-    0x4c35924d,
-    0x4c361269,
-    0x4c36927c,
-    0x4c37128b,
-    0x4c379299,
-    0x4c3812ae,
-    0x4c3892ba,
-    0x4c3912da,
-    0x4c399304,
-    0x4c3a131d,
-    0x4c3a9336,
+    0x4c351260,
+    0x4c35926e,
+    0x4c36128a,
+    0x4c36929d,
+    0x4c3712ac,
+    0x4c3792ba,
+    0x4c3812cf,
+    0x4c3892db,
+    0x4c3912fb,
+    0x4c399325,
+    0x4c3a133e,
+    0x4c3a9357,
     0x4c3b05fb,
-    0x4c3b934f,
-    0x4c3c1361,
-    0x4c3c9370,
-    0x4c3d1389,
-    0x4c3d9398,
-    0x4c3e13a5,
-    0x50322c84,
-    0x5032ac93,
-    0x50332c9e,
-    0x5033acae,
-    0x50342cc7,
-    0x5034ace1,
-    0x50352cef,
-    0x5035ad05,
-    0x50362d17,
-    0x5036ad2d,
-    0x50372d46,
-    0x5037ad59,
-    0x50382d71,
-    0x5038ad82,
-    0x50392d97,
-    0x5039adab,
-    0x503a2dcb,
-    0x503aade1,
-    0x503b2df9,
-    0x503bae0b,
-    0x503c2e27,
-    0x503cae3e,
-    0x503d2e57,
-    0x503dae6d,
-    0x503e2e7a,
-    0x503eae90,
-    0x503f2ea2,
+    0x4c3b9370,
+    0x4c3c1382,
+    0x4c3c9391,
+    0x4c3d13aa,
+    0x4c3d8c29,
+    0x4c3e1403,
+    0x4c3e93b9,
+    0x4c3f1425,
+    0x4c3f91d3,
+    0x4c4013cf,
+    0x4c4091fd,
+    0x4c4113f3,
+    0x50322d49,
+    0x5032ad58,
+    0x50332d63,
+    0x5033ad73,
+    0x50342d8c,
+    0x5034ada6,
+    0x50352db4,
+    0x5035adca,
+    0x50362ddc,
+    0x5036adf2,
+    0x50372e0b,
+    0x5037ae1e,
+    0x50382e36,
+    0x5038ae47,
+    0x50392e5c,
+    0x5039ae70,
+    0x503a2e90,
+    0x503aaea6,
+    0x503b2ebe,
+    0x503baed0,
+    0x503c2eec,
+    0x503caf03,
+    0x503d2f1c,
+    0x503daf32,
+    0x503e2f3f,
+    0x503eaf55,
+    0x503f2f67,
     0x503f8382,
-    0x50402eb5,
-    0x5040aec5,
-    0x50412edf,
-    0x5041aeee,
-    0x50422f08,
-    0x5042af25,
-    0x50432f35,
-    0x5043af45,
-    0x50442f54,
+    0x50402f7a,
+    0x5040af8a,
+    0x50412fa4,
+    0x5041afb3,
+    0x50422fcd,
+    0x5042afea,
+    0x50432ffa,
+    0x5043b00a,
+    0x50443019,
     0x5044843f,
-    0x50452f68,
-    0x5045af86,
-    0x50462f99,
-    0x5046afaf,
-    0x50472fc1,
-    0x5047afd6,
-    0x50482ffc,
-    0x5048b00a,
-    0x5049301d,
-    0x5049b032,
-    0x504a3048,
-    0x504ab058,
-    0x504b3078,
-    0x504bb08b,
-    0x504c30ae,
-    0x504cb0dc,
-    0x504d30ee,
-    0x504db10b,
-    0x504e3126,
-    0x504eb142,
-    0x504f3154,
-    0x504fb16b,
-    0x5050317a,
+    0x5045302d,
+    0x5045b04b,
+    0x5046305e,
+    0x5046b074,
+    0x50473086,
+    0x5047b09b,
+    0x504830c1,
+    0x5048b0cf,
+    0x504930e2,
+    0x5049b0f7,
+    0x504a310d,
+    0x504ab11d,
+    0x504b313d,
+    0x504bb150,
+    0x504c3173,
+    0x504cb1a1,
+    0x504d31b3,
+    0x504db1d0,
+    0x504e31eb,
+    0x504eb207,
+    0x504f3219,
+    0x504fb230,
+    0x5050323f,
     0x505086ef,
-    0x5051318d,
-    0x58320ee5,
-    0x68320ea7,
-    0x68328c41,
-    0x68330c54,
-    0x68338eb5,
-    0x68340ec5,
+    0x50513252,
+    0x58320ef2,
+    0x68320eb4,
+    0x68328c4e,
+    0x68330c61,
+    0x68338ec2,
+    0x68340ed2,
     0x683480ea,
-    0x6c320e83,
+    0x6c320e90,
     0x6c328c18,
-    0x6c330e8e,
+    0x6c330e9b,
     0x74320a0b,
+    0x743280ac,
+    0x74330c29,
     0x78320970,
     0x78328985,
     0x78330991,
@@ -694,7 +705,7 @@
     0x783d0b19,
     0x783d8b2e,
     0x783e0a84,
-    0x7c3210f2,
+    0x7c3210ff,
 };
 
 const size_t kOpenSSLReasonValuesLen = sizeof(kOpenSSLReasonValues) / sizeof(kOpenSSLReasonValues[0]);
@@ -862,6 +873,7 @@
     "INVALID_PUBKEY\0"
     "MODULUS_TOO_LARGE\0"
     "NO_PRIVATE_VALUE\0"
+    "UNKNOWN_HASH\0"
     "BAD_Q_VALUE\0"
     "BAD_VERSION\0"
     "MISSING_PARAMETERS\0"
@@ -939,6 +951,7 @@
     "SHORT_HEADER\0"
     "UNSUPPORTED_CIPHER\0"
     "UNSUPPORTED_ENCRYPTION\0"
+    "BAD_ITERATION_COUNT\0"
     "BAD_PKCS12_DATA\0"
     "BAD_PKCS12_VERSION\0"
     "CIPHER_HAS_NO_OBJECT_IDENTIFIER\0"
@@ -959,8 +972,11 @@
     "UNKNOWN_CIPHER\0"
     "UNKNOWN_CIPHER_ALGORITHM\0"
     "UNKNOWN_DIGEST\0"
-    "UNKNOWN_HASH\0"
+    "UNSUPPORTED_KEYLENGTH\0"
+    "UNSUPPORTED_KEY_DERIVATION_FUNCTION\0"
+    "UNSUPPORTED_PRF\0"
     "UNSUPPORTED_PRIVATE_KEY_ALGORITHM\0"
+    "UNSUPPORTED_SALT_TYPE\0"
     "BAD_E_VALUE\0"
     "BAD_FIXED_HEADER_DECRYPT\0"
     "BAD_PAD_BYTE_COUNT\0"
@@ -1000,6 +1016,7 @@
     "UNKNOWN_PADDING_TYPE\0"
     "VALUE_MISSING\0"
     "WRONG_SIGNATURE_LENGTH\0"
+    "ALPN_MISMATCH_ON_EARLY_DATA\0"
     "APP_DATA_IN_HANDSHAKE\0"
     "ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT\0"
     "BAD_ALERT\0"
@@ -1031,6 +1048,7 @@
     "CERT_CB_ERROR\0"
     "CERT_LENGTH_MISMATCH\0"
     "CHANNEL_ID_NOT_P256\0"
+    "CHANNEL_ID_ON_EARLY_DATA\0"
     "CHANNEL_ID_SIGNATURE_INVALID\0"
     "CIPHER_OR_HASH_UNAVAILABLE\0"
     "CLIENTHELLO_PARSE_FAILED\0"
@@ -1198,6 +1216,7 @@
     "WRONG_SIGNATURE_TYPE\0"
     "WRONG_SSL_VERSION\0"
     "WRONG_VERSION_NUMBER\0"
+    "WRONG_VERSION_ON_EARLY_DATA\0"
     "X509_LIB\0"
     "X509_VERIFICATION_SETUP_PROBLEMS\0"
     "AKID_MISMATCH\0"
diff --git a/sources.bp b/sources.bp
index d40ff4c..cad184c 100644
--- a/sources.bp
+++ b/sources.bp
@@ -173,8 +173,8 @@
         "src/crypto/pem/pem_x509.c",
         "src/crypto/pem/pem_xaux.c",
         "src/crypto/pkcs8/p5_pbev2.c",
-        "src/crypto/pkcs8/p8_pkey.c",
         "src/crypto/pkcs8/pkcs8.c",
+        "src/crypto/pkcs8/pkcs8_x509.c",
         "src/crypto/poly1305/poly1305.c",
         "src/crypto/poly1305/poly1305_arm.c",
         "src/crypto/poly1305/poly1305_vec.c",
@@ -465,7 +465,9 @@
     name: "boringssl_crypto_test_sources",
     srcs: [
         "src/crypto/asn1/asn1_test.cc",
+        "src/crypto/bio/bio_test.cc",
         "src/crypto/chacha/chacha_test.cc",
+        "src/crypto/constant_time_test.cc",
         "src/crypto/curve25519/x25519_test.cc",
         "src/crypto/dh/dh_test.cc",
         "src/crypto/dsa/dsa_test.cc",
@@ -490,13 +492,11 @@
     srcs: [
         "src/crypto/aes/aes_test.cc",
         "src/crypto/base64/base64_test.cc",
-        "src/crypto/bio/bio_test.cc",
         "src/crypto/bn/bn_test.cc",
         "src/crypto/bytestring/bytestring_test.cc",
         "src/crypto/cipher/aead_test.cc",
         "src/crypto/cipher/cipher_test.cc",
         "src/crypto/cmac/cmac_test.cc",
-        "src/crypto/constant_time_test.cc",
         "src/crypto/curve25519/ed25519_test.cc",
         "src/crypto/curve25519/spake25519_test.cc",
         "src/crypto/digest/digest_test.cc",
diff --git a/sources.mk b/sources.mk
index bfc9b7b..d3d689d 100644
--- a/sources.mk
+++ b/sources.mk
@@ -171,8 +171,8 @@
   src/crypto/pem/pem_x509.c\
   src/crypto/pem/pem_xaux.c\
   src/crypto/pkcs8/p5_pbev2.c\
-  src/crypto/pkcs8/p8_pkey.c\
   src/crypto/pkcs8/pkcs8.c\
+  src/crypto/pkcs8/pkcs8_x509.c\
   src/crypto/poly1305/poly1305.c\
   src/crypto/poly1305/poly1305_arm.c\
   src/crypto/poly1305/poly1305_vec.c\
diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt
index 81efad4..3728e6f 100644
--- a/src/crypto/CMakeLists.txt
+++ b/src/crypto/CMakeLists.txt
@@ -177,17 +177,6 @@
 endif()
 
 add_executable(
-  constant_time_test
-
-  constant_time_test.cc
-
-  $<TARGET_OBJECTS:test_support>
-)
-
-target_link_libraries(constant_time_test crypto)
-add_dependencies(all_tests constant_time_test)
-
-add_executable(
   thread_test
 
   thread_test.c
@@ -212,7 +201,9 @@
   crypto_test
 
   asn1/asn1_test.cc
+  bio/bio_test.cc
   chacha/chacha_test.cc
+  constant_time_test.cc
   curve25519/x25519_test.cc
   dh/dh_test.cc
   dsa/dsa_test.cc
@@ -226,4 +217,7 @@
 )
 
 target_link_libraries(crypto_test crypto gtest)
+if (WIN32)
+  target_link_libraries(crypto_test ws2_32)
+endif()
 add_dependencies(all_tests crypto_test)
diff --git a/src/crypto/asn1/a_time.c b/src/crypto/asn1/a_time.c
index 4b58429..c962c0b 100644
--- a/src/crypto/asn1/a_time.c
+++ b/src/crypto/asn1/a_time.c
@@ -114,7 +114,7 @@
 ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(ASN1_TIME *t,
                                                    ASN1_GENERALIZEDTIME **out)
 {
-    ASN1_GENERALIZEDTIME *ret;
+    ASN1_GENERALIZEDTIME *ret = NULL;
     char *str;
     int newlen;
 
@@ -123,22 +123,21 @@
 
     if (!out || !*out) {
         if (!(ret = ASN1_GENERALIZEDTIME_new()))
-            return NULL;
-        if (out)
-            *out = ret;
-    } else
+            goto err;
+    } else {
         ret = *out;
+    }
 
     /* If already GeneralizedTime just copy across */
     if (t->type == V_ASN1_GENERALIZEDTIME) {
         if (!ASN1_STRING_set(ret, t->data, t->length))
-            return NULL;
-        return ret;
+            goto err;
+        goto done;
     }
 
     /* grow the string */
     if (!ASN1_STRING_set(ret, NULL, t->length + 2))
-        return NULL;
+        goto err;
     /* ASN1_STRING_set() allocated 'len + 1' bytes. */
     newlen = t->length + 2 + 1;
     str = (char *)ret->data;
@@ -150,9 +149,18 @@
 
     BUF_strlcat(str, (char *)t->data, newlen);
 
-    return ret;
+ done:
+   if (out != NULL && *out == NULL)
+       *out = ret;
+   return ret;
+
+ err:
+    if (out == NULL || *out != ret)
+        ASN1_GENERALIZEDTIME_free(ret);
+    return NULL;
 }
 
+
 int ASN1_TIME_set_string(ASN1_TIME *s, const char *str)
 {
     ASN1_TIME t;
diff --git a/src/crypto/bio/CMakeLists.txt b/src/crypto/bio/CMakeLists.txt
index 49b9d76..fccb152 100644
--- a/src/crypto/bio/CMakeLists.txt
+++ b/src/crypto/bio/CMakeLists.txt
@@ -16,17 +16,3 @@
   socket.c
   socket_helper.c
 )
-
-add_executable(
-  bio_test
-
-  bio_test.cc
-
-  $<TARGET_OBJECTS:test_support>
-)
-
-target_link_libraries(bio_test crypto)
-if (WIN32)
-  target_link_libraries(bio_test ws2_32)
-endif()
-add_dependencies(all_tests bio_test)
diff --git a/src/crypto/bio/bio_test.cc b/src/crypto/bio/bio_test.cc
index fbfacf8..beb182b 100644
--- a/src/crypto/bio/bio_test.cc
+++ b/src/crypto/bio/bio_test.cc
@@ -16,7 +16,18 @@
 #define _POSIX_C_SOURCE 201410L
 #endif
 
-#include <openssl/base.h>
+#include <algorithm>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/mem.h>
+
+#include "../internal.h"
+#include "../test/test_util.h"
 
 #if !defined(OPENSSL_WINDOWS)
 #include <arpa/inet.h>
@@ -33,27 +44,15 @@
 OPENSSL_MSVC_PRAGMA(warning(pop))
 #endif
 
-#include <openssl/bio.h>
-#include <openssl/crypto.h>
-#include <openssl/err.h>
-#include <openssl/mem.h>
-
-#include <algorithm>
-
-#include "../internal.h"
-
 
 #if !defined(OPENSSL_WINDOWS)
-static int closesocket(int sock) {
-  return close(sock);
-}
-
-static void PrintSocketError(const char *func) {
-  perror(func);
-}
+static int closesocket(int sock) { return close(sock); }
+static std::string LastSocketError() { return strerror(errno); }
 #else
-static void PrintSocketError(const char *func) {
-  fprintf(stderr, "%s: %d\n", func, WSAGetLastError());
+static std::string LastSocketError() {
+  char buf[DECIMAL_SIZE(int) + 1];
+  BIO_snprintf(buf, sizeof(buf), "%d", WSAGetLastError());
+  return buf;
 }
 #endif
 
@@ -68,356 +67,246 @@
   const int sock_;
 };
 
-static bool TestSocketConnect() {
+TEST(BIOTest, SocketConnect) {
   static const char kTestMessage[] = "test";
 
+  // Set up a listening socket on localhost.
   int listening_sock = socket(AF_INET, SOCK_STREAM, 0);
-  if (listening_sock == -1) {
-    PrintSocketError("socket");
-    return false;
-  }
+  ASSERT_NE(-1, listening_sock) << LastSocketError();
   ScopedSocket listening_sock_closer(listening_sock);
 
   struct sockaddr_in sin;
   OPENSSL_memset(&sin, 0, sizeof(sin));
   sin.sin_family = AF_INET;
-  if (!inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr)) {
-    PrintSocketError("inet_pton");
-    return false;
-  }
-  if (bind(listening_sock, (struct sockaddr *)&sin, sizeof(sin)) != 0) {
-    PrintSocketError("bind");
-    return false;
-  }
-  if (listen(listening_sock, 1)) {
-    PrintSocketError("listen");
-    return false;
-  }
+  ASSERT_EQ(1, inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr))
+      << LastSocketError();
+  ASSERT_EQ(0, bind(listening_sock, (struct sockaddr *)&sin, sizeof(sin)))
+      << LastSocketError();
+  ASSERT_EQ(0, listen(listening_sock, 1)) << LastSocketError();
   socklen_t sockaddr_len = sizeof(sin);
-  if (getsockname(listening_sock, (struct sockaddr *)&sin, &sockaddr_len) ||
-      sockaddr_len != sizeof(sin)) {
-    PrintSocketError("getsockname");
-    return false;
-  }
+  ASSERT_EQ(0,
+            getsockname(listening_sock, (struct sockaddr *)&sin, &sockaddr_len))
+      << LastSocketError();
+  // The Android NDK, contrary to POSIX, makes |socklen_t| signed.
+  ASSERT_EQ(sizeof(sin), static_cast<size_t>(sockaddr_len));
 
+  // Connect to it with a connect BIO.
   char hostname[80];
   BIO_snprintf(hostname, sizeof(hostname), "%s:%d", "127.0.0.1",
                ntohs(sin.sin_port));
   bssl::UniquePtr<BIO> bio(BIO_new_connect(hostname));
-  if (!bio) {
-    fprintf(stderr, "BIO_new_connect failed.\n");
-    return false;
-  }
+  ASSERT_TRUE(bio);
 
-  if (BIO_write(bio.get(), kTestMessage, sizeof(kTestMessage)) !=
-      sizeof(kTestMessage)) {
-    fprintf(stderr, "BIO_write failed.\n");
-    ERR_print_errors_fp(stderr);
-    return false;
-  }
+  // Write a test message to the BIO.
+  ASSERT_EQ(static_cast<int>(sizeof(kTestMessage)),
+            BIO_write(bio.get(), kTestMessage, sizeof(kTestMessage)));
 
+  // Accept the socket.
   int sock = accept(listening_sock, (struct sockaddr *) &sin, &sockaddr_len);
-  if (sock == -1) {
-    PrintSocketError("accept");
-    return false;
-  }
+  ASSERT_NE(-1, sock) << LastSocketError();
   ScopedSocket sock_closer(sock);
 
-  char buf[5];
-  if (recv(sock, buf, sizeof(buf), 0) != sizeof(kTestMessage)) {
-    PrintSocketError("read");
-    return false;
-  }
-  if (OPENSSL_memcmp(buf, kTestMessage, sizeof(kTestMessage))) {
-    return false;
-  }
-
-  return true;
+  // Check the same message is read back out.
+  char buf[sizeof(kTestMessage)];
+  ASSERT_EQ(static_cast<int>(sizeof(kTestMessage)),
+            recv(sock, buf, sizeof(buf), 0))
+      << LastSocketError();
+  EXPECT_EQ(Bytes(kTestMessage, sizeof(kTestMessage)), Bytes(buf, sizeof(buf)));
 }
 
-static bool TestPrintf() {
+TEST(BIOTest, Printf) {
   // Test a short output, a very long one, and various sizes around
   // 256 (the size of the buffer) to ensure edge cases are correct.
-  static const size_t kLengths[] = { 5, 250, 251, 252, 253, 254, 1023 };
+  static const size_t kLengths[] = {5, 250, 251, 252, 253, 254, 1023};
 
   bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
-  if (!bio) {
-    fprintf(stderr, "BIO_new failed\n");
-    return false;
-  }
+  ASSERT_TRUE(bio);
 
-  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kLengths); i++) {
-    char string[1024];
-    if (kLengths[i] >= sizeof(string)) {
-      fprintf(stderr, "Bad test string length\n");
-      return false;
-    }
-    OPENSSL_memset(string, 'a', sizeof(string));
-    string[kLengths[i]] = '\0';
+  for (size_t length : kLengths) {
+    SCOPED_TRACE(length);
 
-    int ret = BIO_printf(bio.get(), "test %s", string);
-    if (ret < 0 || static_cast<size_t>(ret) != 5 + kLengths[i]) {
-      fprintf(stderr, "BIO_printf failed: %d\n", ret);
-      return false;
-    }
+    std::string in(length, 'a');
+
+    int ret = BIO_printf(bio.get(), "test %s", in.c_str());
+    ASSERT_GE(ret, 0);
+    EXPECT_EQ(5 + length, static_cast<size_t>(ret));
+
     const uint8_t *contents;
     size_t len;
-    if (!BIO_mem_contents(bio.get(), &contents, &len)) {
-      fprintf(stderr, "BIO_mem_contents failed\n");
-      return false;
-    }
-    if (len != 5 + kLengths[i] ||
-        strncmp((const char *)contents, "test ", 5) != 0 ||
-        strncmp((const char *)contents + 5, string, kLengths[i]) != 0) {
-      fprintf(stderr, "Contents did not match: %.*s\n", (int)len, contents);
-      return false;
-    }
+    ASSERT_TRUE(BIO_mem_contents(bio.get(), &contents, &len));
+    EXPECT_EQ("test " + in,
+              std::string(reinterpret_cast<const char *>(contents), len));
 
-    if (!BIO_reset(bio.get())) {
-      fprintf(stderr, "BIO_reset failed\n");
-      return false;
-    }
+    ASSERT_TRUE(BIO_reset(bio.get()));
   }
-
-  return true;
 }
 
-static bool ReadASN1(bool should_succeed, const uint8_t *data, size_t data_len,
-                     size_t expected_len, size_t max_len) {
-  bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(data, data_len));
+static const size_t kLargeASN1PayloadLen = 8000;
+
+struct ASN1TestParam {
+  bool should_succeed;
+  std::vector<uint8_t> input;
+  // suffix_len is the number of zeros to append to |input|.
+  size_t suffix_len;
+  // expected_len, if |should_succeed| is true, is the expected length of the
+  // ASN.1 element.
+  size_t expected_len;
+  size_t max_len;
+} kASN1TestParams[] = {
+    {true, {0x30, 2, 1, 2, 0, 0}, 0, 4, 100},
+    {false /* truncated */, {0x30, 3, 1, 2}, 0, 0, 100},
+    {false /* should be short len */, {0x30, 0x81, 1, 1}, 0, 0, 100},
+    {false /* zero padded */, {0x30, 0x82, 0, 1, 1}, 0, 0, 100},
+
+    // Test a large payload.
+    {true,
+     {0x30, 0x82, kLargeASN1PayloadLen >> 8, kLargeASN1PayloadLen & 0xff},
+     kLargeASN1PayloadLen,
+     4 + kLargeASN1PayloadLen,
+     kLargeASN1PayloadLen * 2},
+    {false /* max_len too short */,
+     {0x30, 0x82, kLargeASN1PayloadLen >> 8, kLargeASN1PayloadLen & 0xff},
+     kLargeASN1PayloadLen,
+     4 + kLargeASN1PayloadLen,
+     3 + kLargeASN1PayloadLen},
+
+    // Test an indefinite-length input.
+    {true,
+     {0x30, 0x80},
+     kLargeASN1PayloadLen + 2,
+     2 + kLargeASN1PayloadLen + 2,
+     kLargeASN1PayloadLen * 2},
+    {false /* max_len too short */,
+     {0x30, 0x80},
+     kLargeASN1PayloadLen + 2,
+     2 + kLargeASN1PayloadLen + 2,
+     2 + kLargeASN1PayloadLen + 1},
+};
+
+class BIOASN1Test : public testing::TestWithParam<ASN1TestParam> {};
+
+TEST_P(BIOASN1Test, ReadASN1) {
+  const ASN1TestParam& param = GetParam();
+  std::vector<uint8_t> input = param.input;
+  input.resize(input.size() + param.suffix_len, 0);
+
+  bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(input.data(), input.size()));
+  ASSERT_TRUE(bio);
 
   uint8_t *out;
   size_t out_len;
-  int ok = BIO_read_asn1(bio.get(), &out, &out_len, max_len);
+  int ok = BIO_read_asn1(bio.get(), &out, &out_len, param.max_len);
   if (!ok) {
     out = nullptr;
   }
   bssl::UniquePtr<uint8_t> out_storage(out);
 
-  if (should_succeed != (ok == 1)) {
-    return false;
+  ASSERT_EQ(param.should_succeed, (ok == 1));
+  if (param.should_succeed) {
+    EXPECT_EQ(Bytes(input.data(), param.expected_len), Bytes(out, out_len));
   }
-
-  if (should_succeed && (out_len != expected_len ||
-                         OPENSSL_memcmp(data, out, expected_len) != 0)) {
-    return false;
-  }
-
-  return true;
 }
 
-static bool TestASN1() {
-  static const uint8_t kData1[] = {0x30, 2, 1, 2, 0, 0};
-  static const uint8_t kData2[] = {0x30, 3, 1, 2};  /* truncated */
-  static const uint8_t kData3[] = {0x30, 0x81, 1, 1};  /* should be short len */
-  static const uint8_t kData4[] = {0x30, 0x82, 0, 1, 1};  /* zero padded. */
+INSTANTIATE_TEST_CASE_P(, BIOASN1Test, testing::ValuesIn(kASN1TestParams));
 
-  if (!ReadASN1(true, kData1, sizeof(kData1), 4, 100) ||
-      !ReadASN1(false, kData2, sizeof(kData2), 0, 100) ||
-      !ReadASN1(false, kData3, sizeof(kData3), 0, 100) ||
-      !ReadASN1(false, kData4, sizeof(kData4), 0, 100)) {
-    return false;
+// Run through the tests twice, swapping |bio1| and |bio2|, for symmetry.
+class BIOPairTest : public testing::TestWithParam<bool> {};
+
+TEST_P(BIOPairTest, TestPair) {
+  BIO *bio1, *bio2;
+  ASSERT_TRUE(BIO_new_bio_pair(&bio1, 10, &bio2, 10));
+  bssl::UniquePtr<BIO> free_bio1(bio1), free_bio2(bio2);
+
+  if (GetParam()) {
+    std::swap(bio1, bio2);
   }
 
-  static const size_t kLargePayloadLen = 8000;
-  static const uint8_t kLargePrefix[] = {0x30, 0x82, kLargePayloadLen >> 8,
-                                         kLargePayloadLen & 0xff};
-  bssl::UniquePtr<uint8_t> large(reinterpret_cast<uint8_t *>(
-      OPENSSL_malloc(sizeof(kLargePrefix) + kLargePayloadLen)));
-  if (!large) {
-    return false;
-  }
-  OPENSSL_memset(large.get() + sizeof(kLargePrefix), 0, kLargePayloadLen);
-  OPENSSL_memcpy(large.get(), kLargePrefix, sizeof(kLargePrefix));
+  // Check initial states.
+  EXPECT_EQ(10u, BIO_ctrl_get_write_guarantee(bio1));
+  EXPECT_EQ(0u, BIO_ctrl_get_read_request(bio1));
 
-  if (!ReadASN1(true, large.get(), sizeof(kLargePrefix) + kLargePayloadLen,
-                sizeof(kLargePrefix) + kLargePayloadLen,
-                kLargePayloadLen * 2)) {
-    fprintf(stderr, "Large payload test failed.\n");
-    return false;
-  }
+  // Data written in one end may be read out the other.
+  uint8_t buf[20];
+  EXPECT_EQ(5, BIO_write(bio1, "12345", 5));
+  EXPECT_EQ(5u, BIO_ctrl_get_write_guarantee(bio1));
+  ASSERT_EQ(5, BIO_read(bio2, buf, sizeof(buf)));
+  EXPECT_EQ(Bytes("12345"), Bytes(buf, 5));
+  EXPECT_EQ(10u, BIO_ctrl_get_write_guarantee(bio1));
 
-  if (!ReadASN1(false, large.get(), sizeof(kLargePrefix) + kLargePayloadLen,
-                sizeof(kLargePrefix) + kLargePayloadLen,
-                kLargePayloadLen - 1)) {
-    fprintf(stderr, "max_len test failed.\n");
-    return false;
-  }
+  // Attempting to write more than 10 bytes will write partially.
+  EXPECT_EQ(10, BIO_write(bio1, "1234567890___", 13));
+  EXPECT_EQ(0u, BIO_ctrl_get_write_guarantee(bio1));
+  EXPECT_EQ(-1, BIO_write(bio1, "z", 1));
+  EXPECT_TRUE(BIO_should_write(bio1));
+  ASSERT_EQ(10, BIO_read(bio2, buf, sizeof(buf)));
+  EXPECT_EQ(Bytes("1234567890"), Bytes(buf, 10));
+  EXPECT_EQ(10u, BIO_ctrl_get_write_guarantee(bio1));
 
-  static const uint8_t kIndefPrefix[] = {0x30, 0x80};
-  OPENSSL_memcpy(large.get(), kIndefPrefix, sizeof(kIndefPrefix));
-  if (!ReadASN1(true, large.get(), sizeof(kLargePrefix) + kLargePayloadLen,
-                sizeof(kLargePrefix) + kLargePayloadLen,
-                kLargePayloadLen*2)) {
-    fprintf(stderr, "indefinite length test failed.\n");
-    return false;
-  }
+  // Unsuccessful reads update the read request.
+  EXPECT_EQ(-1, BIO_read(bio2, buf, 5));
+  EXPECT_TRUE(BIO_should_read(bio2));
+  EXPECT_EQ(5u, BIO_ctrl_get_read_request(bio1));
 
-  if (!ReadASN1(false, large.get(), sizeof(kLargePrefix) + kLargePayloadLen,
-                sizeof(kLargePrefix) + kLargePayloadLen,
-                kLargePayloadLen-1)) {
-    fprintf(stderr, "indefinite length, max_len test failed.\n");
-    return false;
-  }
+  // The read request is clamped to the size of the buffer.
+  EXPECT_EQ(-1, BIO_read(bio2, buf, 20));
+  EXPECT_TRUE(BIO_should_read(bio2));
+  EXPECT_EQ(10u, BIO_ctrl_get_read_request(bio1));
 
-  return true;
+  // Data may be written and read in chunks.
+  EXPECT_EQ(5, BIO_write(bio1, "12345", 5));
+  EXPECT_EQ(5u, BIO_ctrl_get_write_guarantee(bio1));
+  EXPECT_EQ(5, BIO_write(bio1, "67890___", 8));
+  EXPECT_EQ(0u, BIO_ctrl_get_write_guarantee(bio1));
+  ASSERT_EQ(3, BIO_read(bio2, buf, 3));
+  EXPECT_EQ(Bytes("123"), Bytes(buf, 3));
+  EXPECT_EQ(3u, BIO_ctrl_get_write_guarantee(bio1));
+  ASSERT_EQ(7, BIO_read(bio2, buf, sizeof(buf)));
+  EXPECT_EQ(Bytes("4567890"), Bytes(buf, 7));
+  EXPECT_EQ(10u, BIO_ctrl_get_write_guarantee(bio1));
+
+  // Successful reads reset the read request.
+  EXPECT_EQ(0u, BIO_ctrl_get_read_request(bio1));
+
+  // Test writes and reads starting in the middle of the ring buffer and
+  // wrapping to front.
+  EXPECT_EQ(8, BIO_write(bio1, "abcdefgh", 8));
+  EXPECT_EQ(2u, BIO_ctrl_get_write_guarantee(bio1));
+  ASSERT_EQ(3, BIO_read(bio2, buf, 3));
+  EXPECT_EQ(Bytes("abc"), Bytes(buf, 3));
+  EXPECT_EQ(5u, BIO_ctrl_get_write_guarantee(bio1));
+  EXPECT_EQ(5, BIO_write(bio1, "ijklm___", 8));
+  EXPECT_EQ(0u, BIO_ctrl_get_write_guarantee(bio1));
+  ASSERT_EQ(10, BIO_read(bio2, buf, sizeof(buf)));
+  EXPECT_EQ(Bytes("defghijklm"), Bytes(buf, 10));
+  EXPECT_EQ(10u, BIO_ctrl_get_write_guarantee(bio1));
+
+  // Data may flow from both ends in parallel.
+  EXPECT_EQ(5, BIO_write(bio1, "12345", 5));
+  EXPECT_EQ(5, BIO_write(bio2, "67890", 5));
+  ASSERT_EQ(5, BIO_read(bio2, buf, sizeof(buf)));
+  EXPECT_EQ(Bytes("12345"), Bytes(buf, 5));
+  ASSERT_EQ(5, BIO_read(bio1, buf, sizeof(buf)));
+  EXPECT_EQ(Bytes("67890"), Bytes(buf, 5));
+
+  // Closing the write end causes an EOF on the read half, after draining.
+  EXPECT_EQ(5, BIO_write(bio1, "12345", 5));
+  EXPECT_TRUE(BIO_shutdown_wr(bio1));
+  ASSERT_EQ(5, BIO_read(bio2, buf, sizeof(buf)));
+  EXPECT_EQ(Bytes("12345"), Bytes(buf, 5));
+  EXPECT_EQ(0, BIO_read(bio2, buf, sizeof(buf)));
+
+  // A closed write end may not be written to.
+  EXPECT_EQ(0u, BIO_ctrl_get_write_guarantee(bio1));
+  EXPECT_EQ(-1, BIO_write(bio1, "_____", 5));
+
+  uint32_t err = ERR_get_error();
+  EXPECT_EQ(ERR_LIB_BIO, ERR_GET_LIB(err));
+  EXPECT_EQ(BIO_R_BROKEN_PIPE, ERR_GET_REASON(err));
+
+  // The other end is still functional.
+  EXPECT_EQ(5, BIO_write(bio2, "12345", 5));
+  ASSERT_EQ(5, BIO_read(bio1, buf, sizeof(buf)));
+  EXPECT_EQ(Bytes("12345"), Bytes(buf, 5));
 }
 
-static bool TestPair() {
-  // Run through the tests twice, swapping |bio1| and |bio2|, for symmetry.
-  for (int i = 0; i < 2; i++) {
-    BIO *bio1, *bio2;
-    if (!BIO_new_bio_pair(&bio1, 10, &bio2, 10)) {
-      return false;
-    }
-    bssl::UniquePtr<BIO> free_bio1(bio1), free_bio2(bio2);
-
-    if (i == 1) {
-      std::swap(bio1, bio2);
-    }
-
-    // Check initial states.
-    if (BIO_ctrl_get_write_guarantee(bio1) != 10 ||
-        BIO_ctrl_get_read_request(bio1) != 0) {
-      return false;
-    }
-
-    // Data written in one end may be read out the other.
-    char buf[20];
-    if (BIO_write(bio1, "12345", 5) != 5 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 5 ||
-        BIO_read(bio2, buf, sizeof(buf)) != 5 ||
-        OPENSSL_memcmp(buf, "12345", 5) != 0 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 10) {
-      return false;
-    }
-
-    // Attempting to write more than 10 bytes will write partially.
-    if (BIO_write(bio1, "1234567890___", 13) != 10 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 0 ||
-        BIO_write(bio1, "z", 1) != -1 ||
-        !BIO_should_write(bio1) ||
-        BIO_read(bio2, buf, sizeof(buf)) != 10 ||
-        OPENSSL_memcmp(buf, "1234567890", 10) != 0 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 10) {
-      return false;
-    }
-
-    // Unsuccessful reads update the read request.
-    if (BIO_read(bio2, buf, 5) != -1 ||
-        !BIO_should_read(bio2) ||
-        BIO_ctrl_get_read_request(bio1) != 5) {
-      return false;
-    }
-
-    // The read request is clamped to the size of the buffer.
-    if (BIO_read(bio2, buf, 20) != -1 ||
-        !BIO_should_read(bio2) ||
-        BIO_ctrl_get_read_request(bio1) != 10) {
-      return false;
-    }
-
-    // Data may be written and read in chunks.
-    if (BIO_write(bio1, "12345", 5) != 5 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 5 ||
-        BIO_write(bio1, "67890___", 8) != 5 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 0 ||
-        BIO_read(bio2, buf, 3) != 3 ||
-        OPENSSL_memcmp(buf, "123", 3) != 0 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 3 ||
-        BIO_read(bio2, buf, sizeof(buf)) != 7 ||
-        OPENSSL_memcmp(buf, "4567890", 7) != 0 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 10) {
-      return false;
-    }
-
-    // Successful reads reset the read request.
-    if (BIO_ctrl_get_read_request(bio1) != 0) {
-      return false;
-    }
-
-    // Test writes and reads starting in the middle of the ring buffer and
-    // wrapping to front.
-    if (BIO_write(bio1, "abcdefgh", 8) != 8 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 2 ||
-        BIO_read(bio2, buf, 3) != 3 ||
-        OPENSSL_memcmp(buf, "abc", 3) != 0 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 5 ||
-        BIO_write(bio1, "ijklm___", 8) != 5 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 0 ||
-        BIO_read(bio2, buf, sizeof(buf)) != 10 ||
-        OPENSSL_memcmp(buf, "defghijklm", 10) != 0 ||
-        BIO_ctrl_get_write_guarantee(bio1) != 10) {
-      return false;
-    }
-
-    // Data may flow from both ends in parallel.
-    if (BIO_write(bio1, "12345", 5) != 5 ||
-        BIO_write(bio2, "67890", 5) != 5 ||
-        BIO_read(bio2, buf, sizeof(buf)) != 5 ||
-        OPENSSL_memcmp(buf, "12345", 5) != 0 ||
-        BIO_read(bio1, buf, sizeof(buf)) != 5 ||
-        OPENSSL_memcmp(buf, "67890", 5) != 0) {
-      return false;
-    }
-
-    // Closing the write end causes an EOF on the read half, after draining.
-    if (BIO_write(bio1, "12345", 5) != 5 ||
-        !BIO_shutdown_wr(bio1) ||
-        BIO_read(bio2, buf, sizeof(buf)) != 5 ||
-        OPENSSL_memcmp(buf, "12345", 5) != 0 ||
-        BIO_read(bio2, buf, sizeof(buf)) != 0) {
-      return false;
-    }
-
-    // A closed write end may not be written to.
-    if (BIO_ctrl_get_write_guarantee(bio1) != 0 ||
-        BIO_write(bio1, "_____", 5) != -1) {
-      return false;
-    }
-
-    uint32_t err = ERR_get_error();
-    if (ERR_GET_LIB(err) != ERR_LIB_BIO ||
-        ERR_GET_REASON(err) != BIO_R_BROKEN_PIPE) {
-      return false;
-    }
-
-    // The other end is still functional.
-    if (BIO_write(bio2, "12345", 5) != 5 ||
-        BIO_read(bio1, buf, sizeof(buf)) != 5 ||
-        OPENSSL_memcmp(buf, "12345", 5) != 0) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-int main() {
-  CRYPTO_library_init();
-
-#if defined(OPENSSL_WINDOWS)
-  // Initialize Winsock.
-  WORD wsa_version = MAKEWORD(2, 2);
-  WSADATA wsa_data;
-  int wsa_err = WSAStartup(wsa_version, &wsa_data);
-  if (wsa_err != 0) {
-    fprintf(stderr, "WSAStartup failed: %d\n", wsa_err);
-    return 1;
-  }
-  if (wsa_data.wVersion != wsa_version) {
-    fprintf(stderr, "Didn't get expected version: %x\n", wsa_data.wVersion);
-    return 1;
-  }
-#endif
-
-  if (!TestSocketConnect() ||
-      !TestPrintf() ||
-      !TestASN1() ||
-      !TestPair()) {
-    return 1;
-  }
-
-  printf("PASS\n");
-  return 0;
-}
+INSTANTIATE_TEST_CASE_P(, BIOPairTest, testing::Values(false, true));
diff --git a/src/crypto/constant_time_test.cc b/src/crypto/constant_time_test.cc
index bc127c1..adfe272 100644
--- a/src/crypto/constant_time_test.cc
+++ b/src/crypto/constant_time_test.cc
@@ -49,183 +49,34 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#include <gtest/gtest.h>
 
-static const unsigned int CONSTTIME_TRUE = (unsigned)(~0);
-static const unsigned int CONSTTIME_FALSE = 0;
+
+static const unsigned CONSTTIME_TRUE = (unsigned)(~0);
+static const unsigned CONSTTIME_FALSE = 0;
 static const uint8_t CONSTTIME_TRUE_8 = 0xff;
 static const uint8_t CONSTTIME_FALSE_8 = 0;
 
-static int test_binary_op(unsigned int (*op)(unsigned int a, unsigned int b),
-                          const char* op_name, unsigned int a, unsigned int b,
-                          int is_true) {
-  unsigned c = op(a, b);
-  if (is_true && c != CONSTTIME_TRUE) {
-    fprintf(stderr,
-            "Test failed for %s(%du, %du): expected %du (TRUE), got %du\n",
-            op_name, a, b, CONSTTIME_TRUE, c);
-    return 1;
-  } else if (!is_true && c != CONSTTIME_FALSE) {
-    fprintf(stderr,
-            "Test failed for  %s(%du, %du): expected %du (FALSE), got %du\n",
-            op_name, a, b, CONSTTIME_FALSE, c);
-    return 1;
-  }
-  return 0;
+static unsigned FromBool(bool b) {
+  return b ? CONSTTIME_TRUE : CONSTTIME_FALSE;
 }
 
-static int test_binary_op_8(uint8_t (*op)(unsigned int a, unsigned int b),
-                            const char* op_name, unsigned int a, unsigned int b,
-                            int is_true) {
-  uint8_t c = op(a, b);
-  if (is_true && c != CONSTTIME_TRUE_8) {
-    fprintf(stderr,
-            "Test failed for %s(%du, %du): expected %u (TRUE), got %u\n",
-            op_name, a, b, CONSTTIME_TRUE_8, c);
-    return 1;
-  } else if (!is_true && c != CONSTTIME_FALSE_8) {
-    fprintf(stderr,
-            "Test failed for  %s(%du, %du): expected %u (FALSE), got %u\n",
-            op_name, a, b, CONSTTIME_FALSE_8, c);
-    return 1;
-  }
-  return 0;
+static uint8_t FromBool8(bool b) {
+  return b ? CONSTTIME_TRUE_8 : CONSTTIME_FALSE_8;
 }
 
-static int test_is_zero(unsigned int a) {
-  unsigned int c = constant_time_is_zero(a);
-  if (a == 0 && c != CONSTTIME_TRUE) {
-    fprintf(stderr,
-            "Test failed for constant_time_is_zero(%du): expected %du (TRUE), "
-            "got %du\n",
-            a, CONSTTIME_TRUE, c);
-    return 1;
-  } else if (a != 0 && c != CONSTTIME_FALSE) {
-    fprintf(stderr,
-            "Test failed for constant_time_is_zero(%du): expected %du (FALSE), "
-            "got %du\n",
-            a, CONSTTIME_FALSE, c);
-    return 1;
-  }
-  return 0;
-}
-
-static int test_is_zero_8(unsigned int a) {
-  uint8_t c = constant_time_is_zero_8(a);
-  if (a == 0 && c != CONSTTIME_TRUE_8) {
-    fprintf(stderr,
-            "Test failed for constant_time_is_zero(%du): expected %u (TRUE), "
-            "got %u\n",
-            a, CONSTTIME_TRUE_8, c);
-    return 1;
-  } else if (a != 0 && c != CONSTTIME_FALSE) {
-    fprintf(stderr,
-            "Test failed for constant_time_is_zero(%du): expected %u (FALSE), "
-            "got %u\n",
-            a, CONSTTIME_FALSE_8, c);
-    return 1;
-  }
-  return 0;
-}
-
-static int test_select(unsigned int a, unsigned int b) {
-  unsigned int selected = constant_time_select(CONSTTIME_TRUE, a, b);
-  if (selected != a) {
-    fprintf(stderr,
-            "Test failed for constant_time_select(%du, %du,"
-            "%du): expected %du(first value), got %du\n",
-            CONSTTIME_TRUE, a, b, a, selected);
-    return 1;
-  }
-  selected = constant_time_select(CONSTTIME_FALSE, a, b);
-  if (selected != b) {
-    fprintf(stderr,
-            "Test failed for constant_time_select(%du, %du,"
-            "%du): expected %du(second value), got %du\n",
-            CONSTTIME_FALSE, a, b, b, selected);
-    return 1;
-  }
-  return 0;
-}
-
-static int test_select_8(uint8_t a, uint8_t b) {
-  uint8_t selected = constant_time_select_8(CONSTTIME_TRUE_8, a, b);
-  if (selected != a) {
-    fprintf(stderr,
-            "Test failed for constant_time_select(%u, %u,"
-            "%u): expected %u(first value), got %u\n",
-            CONSTTIME_TRUE, a, b, a, selected);
-    return 1;
-  }
-  selected = constant_time_select_8(CONSTTIME_FALSE_8, a, b);
-  if (selected != b) {
-    fprintf(stderr,
-            "Test failed for constant_time_select(%u, %u,"
-            "%u): expected %u(second value), got %u\n",
-            CONSTTIME_FALSE, a, b, b, selected);
-    return 1;
-  }
-  return 0;
-}
-
-static int test_select_int(int a, int b) {
-  int selected = constant_time_select_int(CONSTTIME_TRUE, a, b);
-  if (selected != a) {
-    fprintf(stderr,
-            "Test failed for constant_time_select(%du, %d,"
-            "%d): expected %d(first value), got %d\n",
-            CONSTTIME_TRUE, a, b, a, selected);
-    return 1;
-  }
-  selected = constant_time_select_int(CONSTTIME_FALSE, a, b);
-  if (selected != b) {
-    fprintf(stderr,
-            "Test failed for constant_time_select(%du, %d,"
-            "%d): expected %d(second value), got %d\n",
-            CONSTTIME_FALSE, a, b, b, selected);
-    return 1;
-  }
-  return 0;
-}
-
-static int test_eq_int(int a, int b) {
-  unsigned int equal = constant_time_eq_int(a, b);
-  if (a == b && equal != CONSTTIME_TRUE) {
-    fprintf(stderr,
-            "Test failed for constant_time_eq_int(%d, %d): expected %du(TRUE), "
-            "got %du\n",
-            a, b, CONSTTIME_TRUE, equal);
-    return 1;
-  } else if (a != b && equal != CONSTTIME_FALSE) {
-    fprintf(stderr,
-            "Test failed for constant_time_eq_int(%d, %d): expected "
-            "%du(FALSE), got %du\n",
-            a, b, CONSTTIME_FALSE, equal);
-    return 1;
-  }
-  return 0;
-}
-
-static int test_eq_int_8(int a, int b) {
-  uint8_t equal = constant_time_eq_int_8(a, b);
-  if (a == b && equal != CONSTTIME_TRUE_8) {
-    fprintf(stderr,
-            "Test failed for constant_time_eq_int_8(%d, %d): expected "
-            "%u(TRUE), got %u\n",
-            a, b, CONSTTIME_TRUE_8, equal);
-    return 1;
-  } else if (a != b && equal != CONSTTIME_FALSE_8) {
-    fprintf(stderr,
-            "Test failed for constant_time_eq_int_8(%d, %d): expected "
-            "%u(FALSE), got %u\n",
-            a, b, CONSTTIME_FALSE_8, equal);
-    return 1;
-  }
-  return 0;
-}
-
-static unsigned int test_values[] = {0, 1, 1024, 12345, 32000, UINT_MAX / 2 - 1,
-                                     UINT_MAX / 2, UINT_MAX / 2 + 1,
-                                     UINT_MAX - 1, UINT_MAX};
+static unsigned test_values[] = {
+    0,
+    1,
+    1024,
+    12345,
+    32000,
+    UINT_MAX / 2 - 1,
+    UINT_MAX / 2,
+    UINT_MAX / 2 + 1,
+    UINT_MAX - 1,
+    UINT_MAX,
+};
 
 static uint8_t test_values_8[] = {0, 1, 2, 20, 32, 127, 128, 129, 255};
 
@@ -233,75 +84,49 @@
     0,     1,      -1,      1024,    -1024,       12345,      -12345,
     32000, -32000, INT_MAX, INT_MIN, INT_MAX - 1, INT_MIN + 1};
 
-int main(int argc, char* argv[]) {
-  unsigned int a, b, i, j;
-  int c, d;
-  uint8_t e, f;
-  int num_failed = 0, num_all = 0;
-  fprintf(stdout, "Testing constant time operations...\n");
+TEST(ConstantTimeTest, Test) {
+  for (unsigned a : test_values) {
+    SCOPED_TRACE(a);
 
-  for (i = 0; i < sizeof(test_values) / sizeof(int); ++i) {
-    a = test_values[i];
-    num_failed += test_is_zero(a);
-    num_failed += test_is_zero_8(a);
-    num_all += 2;
-    for (j = 0; j < sizeof(test_values) / sizeof(int); ++j) {
-      b = test_values[j];
-      num_failed +=
-          test_binary_op(&constant_time_lt, "constant_time_lt", a, b, a < b);
-      num_failed += test_binary_op_8(&constant_time_lt_8, "constant_time_lt_8",
-                                     a, b, a < b);
-      num_failed +=
-          test_binary_op(&constant_time_lt, "constant_time_lt_8", b, a, b < a);
-      num_failed += test_binary_op_8(&constant_time_lt_8, "constant_time_lt_8",
-                                     b, a, b < a);
-      num_failed +=
-          test_binary_op(&constant_time_ge, "constant_time_ge", a, b, a >= b);
-      num_failed += test_binary_op_8(&constant_time_ge_8, "constant_time_ge_8",
-                                     a, b, a >= b);
-      num_failed +=
-          test_binary_op(&constant_time_ge, "constant_time_ge", b, a, b >= a);
-      num_failed += test_binary_op_8(&constant_time_ge_8, "constant_time_ge_8",
-                                     b, a, b >= a);
-      num_failed +=
-          test_binary_op(&constant_time_eq, "constant_time_eq", a, b, a == b);
-      num_failed += test_binary_op_8(&constant_time_eq_8, "constant_time_eq_8",
-                                     a, b, a == b);
-      num_failed +=
-          test_binary_op(&constant_time_eq, "constant_time_eq", b, a, b == a);
-      num_failed += test_binary_op_8(&constant_time_eq_8, "constant_time_eq_8",
-                                     b, a, b == a);
-      num_failed += test_select(a, b);
-      num_all += 13;
+    EXPECT_EQ(FromBool(a == 0), constant_time_is_zero(a));
+    EXPECT_EQ(FromBool8(a == 0), constant_time_is_zero_8(a));
+
+    for (unsigned b : test_values) {
+      SCOPED_TRACE(b);
+
+      EXPECT_EQ(FromBool(a < b), constant_time_lt(a, b));
+      EXPECT_EQ(FromBool8(a < b), constant_time_lt_8(a, b));
+
+      EXPECT_EQ(FromBool(a >= b), constant_time_ge(a, b));
+      EXPECT_EQ(FromBool8(a >= b), constant_time_ge_8(a, b));
+
+      EXPECT_EQ(FromBool(a == b), constant_time_eq(a, b));
+      EXPECT_EQ(FromBool8(a == b), constant_time_eq_8(a, b));
+
+      EXPECT_EQ(a, constant_time_select(CONSTTIME_TRUE, a, b));
+      EXPECT_EQ(b, constant_time_select(CONSTTIME_FALSE, a, b));
     }
   }
 
-  for (i = 0; i < sizeof(signed_test_values) / sizeof(int); ++i) {
-    c = signed_test_values[i];
-    for (j = 0; j < sizeof(signed_test_values) / sizeof(int); ++j) {
-      d = signed_test_values[j];
-      num_failed += test_select_int(c, d);
-      num_failed += test_eq_int(c, d);
-      num_failed += test_eq_int_8(c, d);
-      num_all += 3;
+  for (int a : signed_test_values) {
+    SCOPED_TRACE(a);
+    for (int b : signed_test_values) {
+      SCOPED_TRACE(b);
+
+      EXPECT_EQ(a, constant_time_select_int(CONSTTIME_TRUE, a, b));
+      EXPECT_EQ(b, constant_time_select_int(CONSTTIME_FALSE, a, b));
+
+      EXPECT_EQ(FromBool(a == b), constant_time_eq_int(a, b));
+      EXPECT_EQ(FromBool8(a == b), constant_time_eq_int_8(a, b));
     }
   }
 
-  for (i = 0; i < sizeof(test_values_8); ++i) {
-    e = test_values_8[i];
-    for (j = 0; j < sizeof(test_values_8); ++j) {
-      f = test_values_8[j];
-      num_failed += test_select_8(e, f);
-      num_all += 1;
+  for (uint8_t a : test_values_8) {
+    SCOPED_TRACE(static_cast<int>(a));
+    for (uint8_t b : test_values_8) {
+      SCOPED_TRACE(static_cast<int>(b));
+      EXPECT_EQ(a, constant_time_select_8(CONSTTIME_TRUE_8, a, b));
+      EXPECT_EQ(b, constant_time_select_8(CONSTTIME_FALSE_8, a, b));
     }
   }
-
-  if (!num_failed) {
-    fprintf(stdout, "ok (ran %d tests)\n", num_all);
-    fprintf(stdout, "PASS\n");
-    return EXIT_SUCCESS;
-  } else {
-    fprintf(stdout, "%d of %d tests failed!\n", num_failed, num_all);
-    return EXIT_FAILURE;
-  }
 }
diff --git a/src/crypto/digest/digests.c b/src/crypto/digest/digests.c
index fd2a939..9ad2d49 100644
--- a/src/crypto/digest/digests.c
+++ b/src/crypto/digest/digests.c
@@ -60,6 +60,7 @@
 #include <string.h>
 
 #include <openssl/asn1.h>
+#include <openssl/bytestring.h>
 #include <openssl/md4.h>
 #include <openssl/md5.h>
 #include <openssl/nid.h>
@@ -328,20 +329,58 @@
   { {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04}, 9, EVP_sha224 },
 };
 
+static const EVP_MD *cbs_to_md(const CBS *cbs) {
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kMDOIDs); i++) {
+    if (CBS_len(cbs) == kMDOIDs[i].oid_len &&
+        OPENSSL_memcmp(CBS_data(cbs), kMDOIDs[i].oid, kMDOIDs[i].oid_len) ==
+            0) {
+      return kMDOIDs[i].md_func();
+    }
+  }
+
+  return NULL;
+}
+
 const EVP_MD *EVP_get_digestbyobj(const ASN1_OBJECT *obj) {
   /* Handle objects with no corresponding OID. */
   if (obj->nid != NID_undef) {
     return EVP_get_digestbynid(obj->nid);
   }
 
-  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kMDOIDs); i++) {
-    if (obj->length == kMDOIDs[i].oid_len &&
-        memcmp(obj->data, kMDOIDs[i].oid, obj->length) == 0) {
-      return kMDOIDs[i].md_func();
+  CBS cbs;
+  CBS_init(&cbs, obj->data, obj->length);
+  return cbs_to_md(&cbs);
+}
+
+const EVP_MD *EVP_parse_digest_algorithm(CBS *cbs) {
+  CBS algorithm, oid;
+  if (!CBS_get_asn1(cbs, &algorithm, CBS_ASN1_SEQUENCE) ||
+      !CBS_get_asn1(&algorithm, &oid, CBS_ASN1_OBJECT)) {
+    OPENSSL_PUT_ERROR(DIGEST, DIGEST_R_DECODE_ERROR);
+    return NULL;
+  }
+
+  const EVP_MD *ret = cbs_to_md(&oid);
+  if (ret == NULL) {
+    OPENSSL_PUT_ERROR(DIGEST, DIGEST_R_UNKNOWN_HASH);
+    return NULL;
+  }
+
+  /* The parameters, if present, must be NULL. Historically, whether the NULL
+   * was included or omitted was not well-specified. When parsing an
+   * AlgorithmIdentifier, we allow both. (Note this code is not used when
+   * verifying RSASSA-PKCS1-v1_5 signatures.) */
+  if (CBS_len(&algorithm) > 0) {
+    CBS param;
+    if (!CBS_get_asn1(&algorithm, &param, CBS_ASN1_NULL) ||
+        CBS_len(&param) != 0 ||
+        CBS_len(&algorithm) != 0) {
+      OPENSSL_PUT_ERROR(DIGEST, DIGEST_R_DECODE_ERROR);
+      return NULL;
     }
   }
 
-  return NULL;
+  return ret;
 }
 
 const EVP_MD *EVP_get_digestbyname(const char *name) {
diff --git a/src/crypto/digest/internal.h b/src/crypto/digest/internal.h
index e3d812a..9f83bcb 100644
--- a/src/crypto/digest/internal.h
+++ b/src/crypto/digest/internal.h
@@ -104,6 +104,8 @@
   EVP_PKEY_CTX* (*dup) (EVP_PKEY_CTX *pctx);
 };
 
+const EVP_MD *EVP_parse_digest_algorithm(CBS *cbs);
+
 
 #if defined(__cplusplus)
 }  /* extern C */
diff --git a/src/crypto/err/digest.errordata b/src/crypto/err/digest.errordata
index 411e778..bbcd9cd 100644
--- a/src/crypto/err/digest.errordata
+++ b/src/crypto/err/digest.errordata
@@ -1 +1,3 @@
+DIGEST,101,DECODE_ERROR
 DIGEST,100,INPUT_NOT_INITIALIZED
+DIGEST,102,UNKNOWN_HASH
diff --git a/src/crypto/err/pkcs8.errordata b/src/crypto/err/pkcs8.errordata
index 0eb5083..8b51274 100644
--- a/src/crypto/err/pkcs8.errordata
+++ b/src/crypto/err/pkcs8.errordata
@@ -1,3 +1,4 @@
+PKCS8,129,BAD_ITERATION_COUNT
 PKCS8,100,BAD_PKCS12_DATA
 PKCS8,101,BAD_PKCS12_VERSION
 PKCS8,102,CIPHER_HAS_NO_OBJECT_IDENTIFIER
@@ -22,4 +23,9 @@
 PKCS8,121,UNKNOWN_CIPHER_ALGORITHM
 PKCS8,122,UNKNOWN_DIGEST
 PKCS8,123,UNKNOWN_HASH
+PKCS8,127,UNSUPPORTED_CIPHER
+PKCS8,125,UNSUPPORTED_KEYLENGTH
+PKCS8,128,UNSUPPORTED_KEY_DERIVATION_FUNCTION
+PKCS8,130,UNSUPPORTED_PRF
 PKCS8,124,UNSUPPORTED_PRIVATE_KEY_ALGORITHM
+PKCS8,126,UNSUPPORTED_SALT_TYPE
diff --git a/src/crypto/err/ssl.errordata b/src/crypto/err/ssl.errordata
index 7fddc98..5e38b30 100644
--- a/src/crypto/err/ssl.errordata
+++ b/src/crypto/err/ssl.errordata
@@ -1,3 +1,4 @@
+SSL,277,ALPN_MISMATCH_ON_EARLY_DATA
 SSL,100,APP_DATA_IN_HANDSHAKE
 SSL,101,ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT
 SSL,102,BAD_ALERT
@@ -32,6 +33,7 @@
 SSL,126,CERT_CB_ERROR
 SSL,127,CERT_LENGTH_MISMATCH
 SSL,128,CHANNEL_ID_NOT_P256
+SSL,279,CHANNEL_ID_ON_EARLY_DATA
 SSL,129,CHANNEL_ID_SIGNATURE_INVALID
 SSL,130,CIPHER_OR_HASH_UNAVAILABLE
 SSL,131,CLIENTHELLO_PARSE_FAILED
@@ -204,5 +206,6 @@
 SSL,245,WRONG_SIGNATURE_TYPE
 SSL,246,WRONG_SSL_VERSION
 SSL,247,WRONG_VERSION_NUMBER
+SSL,278,WRONG_VERSION_ON_EARLY_DATA
 SSL,248,X509_LIB
 SSL,249,X509_VERIFICATION_SETUP_PROBLEMS
diff --git a/src/crypto/evp/evp_tests.txt b/src/crypto/evp/evp_tests.txt
index 48121f9..e70ebbc 100644
--- a/src/crypto/evp/evp_tests.txt
+++ b/src/crypto/evp/evp_tests.txt
@@ -27,6 +27,11 @@
 Type = RSA
 Input = 30820154020100300d06092a864886f70d01010105000482013e3082013a020100024100dd20403d976a38c9d79152d87b5c8e9f05033eadd7b7de709bf5b0c4a5182a97d18483526b02362b992e154a9f37faa396ca2685cdab8fec09877ebe705f4dd70203010001024055bebcca655d7e39de8a6eaa9d636db682161907064039544755c53eeb99ec618c03a210dbc61471eaba10c5c365c9726d6b7a96f54d455f7d168d49367270e1022100f21a05d9fd6817301ce49ce10448f9bdd44f5ef5b7557cd7d83155db46382ae7022100e9d1f7157783db2feab1936954ddc4e83aa365695868144cda1be6813b61d791022100d6001eb0040920860ce41fafdf23ca6dfbdf74e6e9f98cf3164cf5c16f9e727d02206f6f73f4b52b10517be6f9bc5f87fa0a3bb817e2e711636b651f9af1c85d4f21022063eff2e57f5b4ca20342cfe793e25526624e3692f192461f9e1ce7f13f2d72c8
 
+# RSA 515 bit key.
+PrivateKey = RSA-515
+Type = RSA
+Input = 30820157020100300d06092a864886f70d0101010500048201413082013d0201000241054fa166e205e658bbe8a2dc35311c0c2b75b7e4569fd9642c8bae809279271fc824f26baa1166ea46298ca63379ea76adbada2b61e5066820a35beaec1aca227f020301000102410266c972be0d30e53ac2acb1aa13b4bd0401cccf212452a66b4615f7e943831f67b4ca48560582d0ca886044aaaaf87945252a848c1947944186e6eb83969bf91102210309e631761842cc8a2ccfd372c20a9cba21de1a199c30ab440bc6b51079f4e825022101bf715c1db432627ca7c29a293b9210f2eff1e92d12f306ebaa5334f8ee03dcd30221018ac58a765f2b8f37d434081fe5ff92b81735ead2f263f4968ccf63d61fbe3d0d0221015b247a1159a2d5a25d0db049593c6405f77f3a278c521d066e290c2a2d8fb59d0221026224aa31fd95c14d24fd03b8a195bba4cc88df7c37f5370a5ab19f882f1404d6
+
 # EC P-256 key
 PrivateKey = P-256
 Type = EC
@@ -177,6 +182,14 @@
 Input = "0123456789ABCDEF0123456789ABCDEF"
 Output = 4de433d5844043ef08d354da03cb29068780d52706d7d1e4d50efb7d58c9d547d83a747ddd0635a96b28f854e50145518482cb49e963054621b53c60c498d07c16e9c2789c893cf38d4d86900de71bde463bd2761d1271e358c7480a1ac0bab930ddf39602ad1bc165b5d7436b516b7a7858e8eb7ab1c420eeb482f4d207f0e462b1724959320a084e13848d11d10fb593e66bf680bf6d3f345fc3e9c3de60abbac37e1c6ec80a268c8d9fc49626c679097aa690bc1aa662b95eb8db70390861aa0898229f9349b4b5fdd030d4928c47084708a933144be23bd3c6e661b85b2c0ef9ed36d498d5b7320e8194d363d4ad478c059bae804181965e0b81b663158a
 
+# Auto-detected salt length
+Verify = RSA-2048-SPKI
+RSAPadding = PSS
+PSSSaltLength = -2
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 4de433d5844043ef08d354da03cb29068780d52706d7d1e4d50efb7d58c9d547d83a747ddd0635a96b28f854e50145518482cb49e963054621b53c60c498d07c16e9c2789c893cf38d4d86900de71bde463bd2761d1271e358c7480a1ac0bab930ddf39602ad1bc165b5d7436b516b7a7858e8eb7ab1c420eeb482f4d207f0e462b1724959320a084e13848d11d10fb593e66bf680bf6d3f345fc3e9c3de60abbac37e1c6ec80a268c8d9fc49626c679097aa690bc1aa662b95eb8db70390861aa0898229f9349b4b5fdd030d4928c47084708a933144be23bd3c6e661b85b2c0ef9ed36d498d5b7320e8194d363d4ad478c059bae804181965e0b81b663158a
+
 # Wrong digest
 Verify = RSA-2048-SPKI
 RSAPadding = PSS
@@ -213,6 +226,15 @@
 Output = 4de433d5844043ef08d354da03cb29068780d52706d7d1e4d50efb7d58c9d547d83a747ddd0635a96b28f854e50145518482cb49e963054621b53c60c498d07c16e9c2789c893cf38d4d86900de71bde463bd2761d1271e358c7480a1ac0bab930ddf39602ad1bc165b5d7436b516b7a7858e8eb7ab1c420eeb482f4d207f0e462b1724959320a084e13848d11d10fb593e66bf680bf6d3f345fc3e9c3de60abbac37e1c6ec80a268c8d9fc49626c679097aa690bc1aa662b95eb8db70390861aa0898229f9349b4b5fdd030d4928c47084708a933144be23bd3c6e661b85b2c0ef9ed36d498d5b7320e8194d363d4ad478c059bae804181965e0b81b663158a
 Error = SLEN_CHECK_FAILED
 
+# Wrong salt length using implicit hash length
+Verify = RSA-2048
+RSAPadding = PSS
+PSSSaltLength = -1
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 4de433d5844043ef08d354da03cb29068780d52706d7d1e4d50efb7d58c9d547d83a747ddd0635a96b28f854e50145518482cb49e963054621b53c60c498d07c16e9c2789c893cf38d4d86900de71bde463bd2761d1271e358c7480a1ac0bab930ddf39602ad1bc165b5d7436b516b7a7858e8eb7ab1c420eeb482f4d207f0e462b1724959320a084e13848d11d10fb593e66bf680bf6d3f345fc3e9c3de60abbac37e1c6ec80a268c8d9fc49626c679097aa690bc1aa662b95eb8db70390861aa0898229f9349b4b5fdd030d4928c47084708a933144be23bd3c6e661b85b2c0ef9ed36d498d5b7320e8194d363d4ad478c059bae804181965e0b81b663158a
+Error = SLEN_CHECK_FAILED
+
 # Wrong MGF1 digest, SHA-1
 Verify = RSA-2048
 RSAPadding = PSS
@@ -285,6 +307,84 @@
 Output = 457001d9ca50a93385fc5ec721c9dbbe7a0f2e9e4a2f846a30a8811dde66347b83901c7492039243537c7a667fafffd69049bcbd36afd0010d9b425e2d8785c1
 Error = DATA_TOO_LARGE
 
+# Sample RSA-515 signature.
+Verify = RSA-515
+RSAPadding = PSS
+PSSSaltLength = 0
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 00c5926600f160f85e7fe950cfe123908384211cd8fe25c90cb8e8cc0593308e9aa2efe3acbf100ec1658ded8f72f506525fc2c44f06251b08d896e7bb3f05b135
+
+# The above, but with too few leading zeros.
+Verify = RSA-515
+RSAPadding = PSS
+PSSSaltLength = 0
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = c5926600f160f85e7fe950cfe123908384211cd8fe25c90cb8e8cc0593308e9aa2efe3acbf100ec1658ded8f72f506525fc2c44f06251b08d896e7bb3f05b135
+Error = DATA_LEN_NOT_EQUAL_TO_MOD_LEN
+
+# The above, but with too many leading zeros.
+Verify = RSA-515
+RSAPadding = PSS
+PSSSaltLength = 0
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 0000c5926600f160f85e7fe950cfe123908384211cd8fe25c90cb8e8cc0593308e9aa2efe3acbf100ec1658ded8f72f506525fc2c44f06251b08d896e7bb3f05b135
+Error = DATA_LEN_NOT_EQUAL_TO_MOD_LEN
+
+# The above with an invalid leading byte. The top few bits of EM are required to
+# be cleared.
+Verify = RSA-515
+RSAPadding = PSS
+PSSSaltLength = 0
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 007f803c832a2090aea04013d9fa9c1630732a1625232826d235f0950f7050d3fb0eb06ef9ea8b260fad68e1165a2d770a8c7fc7a8aaa68620b021fc19c97e0041
+Error = FIRST_OCTET_INVALID
+
+# The above with an invalid trailing byte.
+Verify = RSA-515
+RSAPadding = PSS
+PSSSaltLength = 0
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 03e68555035891eb08d96c0967db22328cd892ad2856d88516ecb946bfdba732bb029b5c0dfa2119ed7349897d2324e95e86d91d0c4afc82700a36db8933abbf58
+Error = LAST_OCTET_INVALID
+
+# Non-zero salt length.
+Verify = RSA-2048-SPKI
+RSAPadding = PSS
+PSSSaltLength = 32
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 4065b284b0a6e98d4c41a8427007f878d8dd61599c87764fa79b8bf03f030c48127a4b1a5af5a6e0cf9055e57a1f47e5b0c0d8c600e78369cf1c39374899fac91a812692aa2216ba10900ce85a5cf7fddcafb726e4b83479c5bb7b3b84b08ffe183b4c2973aa3193ec7b7d4ea73bf1b579c6657b78ad7800e1975a4838c28ffe353fafef96be27b5c69677760a71b6f4df65ba6fe6b3565580a536f966928294c6e9ece807a90c1477779bcbfa3a250e98d685097c162c1c8c56ab02bd2e16eec7a019b51c067bdba7fa8cd5460796e22c607a8b6d12e1deb9be51c6943c46590f416800c48bb4cbb8c409d316573e59eadf7d3b9e6e5c2d0e570692e511e139
+
+# Non-zero salt length, wrong salt length.
+Verify = RSA-2048-SPKI
+RSAPadding = PSS
+PSSSaltLength = 31
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 4065b284b0a6e98d4c41a8427007f878d8dd61599c87764fa79b8bf03f030c48127a4b1a5af5a6e0cf9055e57a1f47e5b0c0d8c600e78369cf1c39374899fac91a812692aa2216ba10900ce85a5cf7fddcafb726e4b83479c5bb7b3b84b08ffe183b4c2973aa3193ec7b7d4ea73bf1b579c6657b78ad7800e1975a4838c28ffe353fafef96be27b5c69677760a71b6f4df65ba6fe6b3565580a536f966928294c6e9ece807a90c1477779bcbfa3a250e98d685097c162c1c8c56ab02bd2e16eec7a019b51c067bdba7fa8cd5460796e22c607a8b6d12e1deb9be51c6943c46590f416800c48bb4cbb8c409d316573e59eadf7d3b9e6e5c2d0e570692e511e139
+Error = SLEN_CHECK_FAILED
+
+# Non-zero salt length, match hash length.
+Verify = RSA-2048-SPKI
+RSAPadding = PSS
+PSSSaltLength = -1
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 4065b284b0a6e98d4c41a8427007f878d8dd61599c87764fa79b8bf03f030c48127a4b1a5af5a6e0cf9055e57a1f47e5b0c0d8c600e78369cf1c39374899fac91a812692aa2216ba10900ce85a5cf7fddcafb726e4b83479c5bb7b3b84b08ffe183b4c2973aa3193ec7b7d4ea73bf1b579c6657b78ad7800e1975a4838c28ffe353fafef96be27b5c69677760a71b6f4df65ba6fe6b3565580a536f966928294c6e9ece807a90c1477779bcbfa3a250e98d685097c162c1c8c56ab02bd2e16eec7a019b51c067bdba7fa8cd5460796e22c607a8b6d12e1deb9be51c6943c46590f416800c48bb4cbb8c409d316573e59eadf7d3b9e6e5c2d0e570692e511e139
+
+# Non-zero salt length, auto-detected.
+Verify = RSA-2048-SPKI
+RSAPadding = PSS
+PSSSaltLength = -2
+Digest = SHA256
+Input = "0123456789ABCDEF0123456789ABCDEF"
+Output = 4065b284b0a6e98d4c41a8427007f878d8dd61599c87764fa79b8bf03f030c48127a4b1a5af5a6e0cf9055e57a1f47e5b0c0d8c600e78369cf1c39374899fac91a812692aa2216ba10900ce85a5cf7fddcafb726e4b83479c5bb7b3b84b08ffe183b4c2973aa3193ec7b7d4ea73bf1b579c6657b78ad7800e1975a4838c28ffe353fafef96be27b5c69677760a71b6f4df65ba6fe6b3565580a536f966928294c6e9ece807a90c1477779bcbfa3a250e98d685097c162c1c8c56ab02bd2e16eec7a019b51c067bdba7fa8cd5460796e22c607a8b6d12e1deb9be51c6943c46590f416800c48bb4cbb8c409d316573e59eadf7d3b9e6e5c2d0e570692e511e139
+
 
 # RSA decrypt
 
diff --git a/src/crypto/pkcs8/CMakeLists.txt b/src/crypto/pkcs8/CMakeLists.txt
index a2e52e1..0bdccec 100644
--- a/src/crypto/pkcs8/CMakeLists.txt
+++ b/src/crypto/pkcs8/CMakeLists.txt
@@ -6,7 +6,7 @@
   OBJECT
 
   pkcs8.c
-  p8_pkey.c
+  pkcs8_x509.c
   p5_pbev2.c
 )
 
diff --git a/src/crypto/pkcs8/internal.h b/src/crypto/pkcs8/internal.h
index 9cebe29..583997d 100644
--- a/src/crypto/pkcs8/internal.h
+++ b/src/crypto/pkcs8/internal.h
@@ -63,35 +63,53 @@
 #endif
 
 
-#define PBE_UCS2_CONVERT_PASSWORD 0x1
+/* pkcs8_pbe_decrypt decrypts |in| using the PBE scheme described by
+ * |algorithm|, which should be a serialized AlgorithmIdentifier structure. On
+ * success, it sets |*out| to a newly-allocated buffer containing the decrypted
+ * result and returns one. Otherwise, it returns zero. */
+int pkcs8_pbe_decrypt(uint8_t **out, size_t *out_len, CBS *algorithm,
+                      const char *pass, size_t pass_len, const uint8_t *in,
+                      size_t in_len);
+
+#define PKCS12_KEY_ID 1
+#define PKCS12_IV_ID 2
+#define PKCS12_MAC_ID 3
+
+/* pkcs12_key_gen runs the PKCS#12 key derivation function as specified in
+ * RFC 7292, appendix B. On success, it writes the resulting |out_len| bytes of
+ * key material to |out| and returns one. Otherwise, it returns zero. |id|
+ * should be one of the |PKCS12_*_ID| values. */
+int pkcs12_key_gen(const char *pass, size_t pass_len, const uint8_t *salt,
+                   size_t salt_len, uint8_t id, unsigned iterations,
+                   size_t out_len, uint8_t *out, const EVP_MD *md);
 
 struct pbe_suite {
   int pbe_nid;
+  uint8_t oid[10];
+  uint8_t oid_len;
   const EVP_CIPHER *(*cipher_func)(void);
   const EVP_MD *(*md_func)(void);
   /* decrypt_init initialize |ctx| for decrypting. The password is specified by
-   * |pass_raw| and |pass_raw_len|. |param| contains the serialized parameters
-   * field of the AlgorithmIdentifier.
+   * |pass| and |pass_len|. |param| contains the serialized parameters field of
+   * the AlgorithmIdentifier.
    *
    * It returns one on success and zero on error. */
   int (*decrypt_init)(const struct pbe_suite *suite, EVP_CIPHER_CTX *ctx,
-                      const uint8_t *pass_raw, size_t pass_raw_len, CBS *param);
-  int flags;
+                      const char *pass, size_t pass_len, CBS *param);
 };
 
 #define PKCS5_DEFAULT_ITERATIONS 2048
 #define PKCS5_SALT_LEN 8
 
 int PKCS5_pbe2_decrypt_init(const struct pbe_suite *suite, EVP_CIPHER_CTX *ctx,
-                            const uint8_t *pass_raw, size_t pass_raw_len,
-                            CBS *param);
+                            const char *pass, size_t pass_len, CBS *param);
 
 /* PKCS5_pbe2_encrypt_init configures |ctx| for encrypting with PKCS #5 PBES2,
  * as defined in RFC 2998, with the specified parameters. It writes the
  * corresponding AlgorithmIdentifier to |out|. */
 int PKCS5_pbe2_encrypt_init(CBB *out, EVP_CIPHER_CTX *ctx,
                             const EVP_CIPHER *cipher, unsigned iterations,
-                            const uint8_t *pass_raw, size_t pass_raw_len,
+                            const char *pass, size_t pass_len,
                             const uint8_t *salt, size_t salt_len);
 
 
diff --git a/src/crypto/pkcs8/p5_pbev2.c b/src/crypto/pkcs8/p5_pbev2.c
index 59e2067..29d8929 100644
--- a/src/crypto/pkcs8/p5_pbev2.c
+++ b/src/crypto/pkcs8/p5_pbev2.c
@@ -62,16 +62,86 @@
 #include <openssl/cipher.h>
 #include <openssl/err.h>
 #include <openssl/mem.h>
-#include <openssl/obj.h>
+#include <openssl/nid.h>
 #include <openssl/rand.h>
 
 #include "internal.h"
 #include "../internal.h"
 
 
+/* 1.2.840.113549.1.5.12 */
+static const uint8_t kPBKDF2[] = {0x2a, 0x86, 0x48, 0x86, 0xf7,
+                                  0x0d, 0x01, 0x05, 0x0c};
+
+/* 1.2.840.113549.1.5.13 */
+static const uint8_t kPBES2[] = {0x2a, 0x86, 0x48, 0x86, 0xf7,
+                                 0x0d, 0x01, 0x05, 0x0d};
+
+/* 1.2.840.113549.2.7 */
+static const uint8_t kHMACWithSHA1[] = {0x2a, 0x86, 0x48, 0x86,
+                                        0xf7, 0x0d, 0x02, 0x07};
+
+static const struct {
+  uint8_t oid[9];
+  uint8_t oid_len;
+  int nid;
+  const EVP_CIPHER *(*cipher_func)(void);
+} kCipherOIDs[] = {
+    /* 1.2.840.113549.3.2 */
+    {{0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x03, 0x02},
+     8,
+     NID_rc2_cbc,
+     &EVP_rc2_cbc},
+    /* 1.2.840.113549.3.7 */
+    {{0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x03, 0x07},
+     8,
+     NID_des_ede3_cbc,
+     &EVP_des_ede3_cbc},
+    /* 2.16.840.1.101.3.4.1.2 */
+    {{0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02},
+     9,
+     NID_aes_128_cbc,
+     &EVP_aes_128_cbc},
+    /* 2.16.840.1.101.3.4.1.22 */
+    {{0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x16},
+     9,
+     NID_aes_192_cbc,
+     &EVP_aes_192_cbc},
+    /* 2.16.840.1.101.3.4.1.42 */
+    {{0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2a},
+     9,
+     NID_aes_256_cbc,
+     &EVP_aes_256_cbc},
+};
+
+static const EVP_CIPHER *cbs_to_cipher(const CBS *cbs) {
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kCipherOIDs); i++) {
+    if (CBS_mem_equal(cbs, kCipherOIDs[i].oid, kCipherOIDs[i].oid_len)) {
+      return kCipherOIDs[i].cipher_func();
+    }
+  }
+
+  return NULL;
+}
+
+static int add_cipher_oid(CBB *out, int nid) {
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kCipherOIDs); i++) {
+    if (kCipherOIDs[i].nid == nid) {
+      CBB child;
+      return CBB_add_asn1(out, &child, CBS_ASN1_OBJECT) &&
+             CBB_add_bytes(&child, kCipherOIDs[i].oid,
+                           kCipherOIDs[i].oid_len) &&
+             CBB_flush(out);
+    }
+  }
+
+  OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_UNSUPPORTED_CIPHER);
+  return 0;
+}
+
 static int pkcs5_pbe2_cipher_init(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
-                                  unsigned iterations, const uint8_t *pass_raw,
-                                  size_t pass_raw_len, const uint8_t *salt,
+                                  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)) {
@@ -80,8 +150,7 @@
   }
 
   uint8_t key[EVP_MAX_KEY_LENGTH];
-  int ret = PKCS5_PBKDF2_HMAC_SHA1((const char *)pass_raw, pass_raw_len, salt,
-                                   salt_len, iterations,
+  int ret = PKCS5_PBKDF2_HMAC_SHA1(pass, pass_len, salt, salt_len, iterations,
                                    EVP_CIPHER_key_length(cipher), key) &&
             EVP_CipherInit_ex(ctx, cipher, NULL /* engine */, key, iv, enc);
   OPENSSL_cleanse(key, EVP_MAX_KEY_LENGTH);
@@ -90,7 +159,7 @@
 
 int PKCS5_pbe2_encrypt_init(CBB *out, EVP_CIPHER_CTX *ctx,
                             const EVP_CIPHER *cipher, unsigned iterations,
-                            const uint8_t *pass_raw, size_t pass_raw_len,
+                            const char *pass, size_t pass_len,
                             const uint8_t *salt, size_t salt_len) {
   int cipher_nid = EVP_CIPHER_nid(cipher);
   if (cipher_nid == NID_undef) {
@@ -105,12 +174,15 @@
   }
 
   /* See RFC 2898, appendix A. */
-  CBB algorithm, param, kdf, kdf_param, salt_cbb, cipher_cbb, iv_cbb;
+  CBB algorithm, oid, param, kdf, kdf_oid, kdf_param, salt_cbb, cipher_cbb,
+      iv_cbb;
   if (!CBB_add_asn1(out, &algorithm, CBS_ASN1_SEQUENCE) ||
-      !OBJ_nid2cbb(&algorithm, NID_pbes2) ||
+      !CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) ||
+      !CBB_add_bytes(&oid, kPBES2, sizeof(kPBES2)) ||
       !CBB_add_asn1(&algorithm, &param, CBS_ASN1_SEQUENCE) ||
       !CBB_add_asn1(&param, &kdf, CBS_ASN1_SEQUENCE) ||
-      !OBJ_nid2cbb(&kdf, NID_id_pbkdf2) ||
+      !CBB_add_asn1(&kdf, &kdf_oid, CBS_ASN1_OBJECT) ||
+      !CBB_add_bytes(&kdf_oid, kPBKDF2, sizeof(kPBKDF2)) ||
       !CBB_add_asn1(&kdf, &kdf_param, CBS_ASN1_SEQUENCE) ||
       !CBB_add_asn1(&kdf_param, &salt_cbb, CBS_ASN1_OCTETSTRING) ||
       !CBB_add_bytes(&salt_cbb, salt, salt_len) ||
@@ -120,7 +192,7 @@
        !CBB_add_asn1_uint64(&kdf_param, EVP_CIPHER_key_length(cipher))) ||
       /* Omit the PRF. We use the default hmacWithSHA1. */
       !CBB_add_asn1(&param, &cipher_cbb, CBS_ASN1_SEQUENCE) ||
-      !OBJ_nid2cbb(&cipher_cbb, cipher_nid) ||
+      !add_cipher_oid(&cipher_cbb, cipher_nid) ||
       /* RFC 2898 says RC2-CBC and RC5-CBC-Pad use a SEQUENCE with version and
        * IV, but OpenSSL always uses an OCTET STRING IV, so we do the same. */
       !CBB_add_asn1(&cipher_cbb, &iv_cbb, CBS_ASN1_OCTETSTRING) ||
@@ -129,14 +201,13 @@
     return 0;
   }
 
-  return pkcs5_pbe2_cipher_init(ctx, cipher, iterations, pass_raw, pass_raw_len,
-                                salt, salt_len, iv,
-                                EVP_CIPHER_iv_length(cipher), 1 /* encrypt */);
+  return pkcs5_pbe2_cipher_init(ctx, cipher, 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,
-                            const uint8_t *pass_raw, size_t pass_raw_len,
-                            CBS *param) {
+                            const char *pass, size_t pass_len, CBS *param) {
   CBS pbe_param, kdf, kdf_obj, enc_scheme, enc_obj;
   if (!CBS_get_asn1(param, &pbe_param, CBS_ASN1_SEQUENCE) ||
       CBS_len(param) != 0 ||
@@ -149,20 +220,20 @@
     return 0;
   }
 
-  /* Check that the key derivation function is PBKDF2. */
-  if (OBJ_cbs2nid(&kdf_obj) != NID_id_pbkdf2) {
+  /* Only PBKDF2 is supported. */
+  if (!CBS_mem_equal(&kdf_obj, kPBKDF2, sizeof(kPBKDF2))) {
     OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_UNSUPPORTED_KEY_DERIVATION_FUNCTION);
     return 0;
   }
 
   /* See if we recognise the encryption algorithm. */
-  const EVP_CIPHER *cipher = EVP_get_cipherbynid(OBJ_cbs2nid(&enc_obj));
+  const EVP_CIPHER *cipher = cbs_to_cipher(&enc_obj);
   if (cipher == NULL) {
     OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_UNSUPPORTED_CIPHER);
     return 0;
   }
 
-  /* Parse the KDF parameters. */
+  /* Parse the KDF parameters. See RFC 8018, appendix A.2. */
   CBS pbkdf2_params, salt;
   uint64_t iterations;
   if (!CBS_get_asn1(&kdf, &pbkdf2_params, CBS_ASN1_SEQUENCE) ||
@@ -194,8 +265,9 @@
   }
 
   if (CBS_len(&pbkdf2_params) != 0) {
-    CBS prf;
-    if (!CBS_get_asn1(&pbkdf2_params, &prf, CBS_ASN1_OBJECT) ||
+    CBS alg_id, prf;
+    if (!CBS_get_asn1(&pbkdf2_params, &alg_id, CBS_ASN1_SEQUENCE) ||
+        !CBS_get_asn1(&alg_id, &prf, CBS_ASN1_OBJECT) ||
         CBS_len(&pbkdf2_params) != 0) {
       OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
       return 0;
@@ -203,16 +275,25 @@
 
     /* We only support hmacWithSHA1. It is the DEFAULT, so DER requires it be
      * omitted, but we match OpenSSL in tolerating it being present. */
-    if (OBJ_cbs2nid(&prf) != NID_hmacWithSHA1) {
+    if (!CBS_mem_equal(&prf, kHMACWithSHA1, sizeof(kHMACWithSHA1))) {
       OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_UNSUPPORTED_PRF);
       return 0;
     }
+
+    /* hmacWithSHA1 has a NULL parameter. */
+    CBS null;
+    if (!CBS_get_asn1(&alg_id, &null, CBS_ASN1_NULL) ||
+        CBS_len(&null) != 0 ||
+        CBS_len(&alg_id) != 0) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
+      return 0;
+    }
   }
 
   /* Parse the encryption scheme parameters. Note OpenSSL does not match the
    * specification. Per RFC 2898, this should depend on the encryption scheme.
-   * In particular, RC2-CBC and RC5-CBC-Pad use a SEQUENCE with version and IV.
-   * We align with OpenSSL. */
+   * In particular, RC2-CBC uses a SEQUENCE with version and IV. We align with
+   * OpenSSL. */
   CBS iv;
   if (!CBS_get_asn1(&enc_scheme, &iv, CBS_ASN1_OCTETSTRING) ||
       CBS_len(&enc_scheme) != 0) {
@@ -220,7 +301,7 @@
     return 0;
   }
 
-  return pkcs5_pbe2_cipher_init(ctx, cipher, (unsigned)iterations, pass_raw,
-                                pass_raw_len, CBS_data(&salt), CBS_len(&salt),
+  return pkcs5_pbe2_cipher_init(ctx, cipher, (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/p8_pkey.c b/src/crypto/pkcs8/p8_pkey.c
deleted file mode 100644
index 69a7e29..0000000
--- a/src/crypto/pkcs8/p8_pkey.c
+++ /dev/null
@@ -1,85 +0,0 @@
-/* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL
- * project 1999.
- */
-/* ====================================================================
- * Copyright (c) 1999-2005 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
- *    licensing@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.
- * ====================================================================
- *
- * This product includes cryptographic software written by Eric Young
- * (eay@cryptsoft.com).  This product includes software written by Tim
- * Hudson (tjh@cryptsoft.com). */
-
-
-#include <stdio.h>
-
-#include <openssl/asn1t.h>
-#include <openssl/mem.h>
-#include <openssl/x509.h>
-
-/* Minor tweak to operation: zero private key data */
-static int pkey_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it,
-                   void *exarg) {
-  /* Since the structure must still be valid use ASN1_OP_FREE_PRE */
-  if (operation == ASN1_OP_FREE_PRE) {
-    PKCS8_PRIV_KEY_INFO *key = (PKCS8_PRIV_KEY_INFO *)*pval;
-    if (key->pkey && key->pkey->type == V_ASN1_OCTET_STRING &&
-        key->pkey->value.octet_string) {
-      OPENSSL_cleanse(key->pkey->value.octet_string->data,
-                      key->pkey->value.octet_string->length);
-    }
-  }
-  return 1;
-}
-
-ASN1_SEQUENCE_cb(PKCS8_PRIV_KEY_INFO, pkey_cb) = {
-  ASN1_SIMPLE(PKCS8_PRIV_KEY_INFO, version, ASN1_INTEGER),
-  ASN1_SIMPLE(PKCS8_PRIV_KEY_INFO, pkeyalg, X509_ALGOR),
-  ASN1_SIMPLE(PKCS8_PRIV_KEY_INFO, pkey, ASN1_ANY),
-  ASN1_IMP_SET_OF_OPT(PKCS8_PRIV_KEY_INFO, attributes, X509_ATTRIBUTE, 0)
-} ASN1_SEQUENCE_END_cb(PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO)
-
-IMPLEMENT_ASN1_FUNCTIONS(PKCS8_PRIV_KEY_INFO)
diff --git a/src/crypto/pkcs8/pkcs12_test.cc b/src/crypto/pkcs8/pkcs12_test.cc
index 5c1a1b4..ba02761 100644
--- a/src/crypto/pkcs8/pkcs12_test.cc
+++ b/src/crypto/pkcs8/pkcs12_test.cc
@@ -679,6 +679,225 @@
     0xfe, 0x3a, 0x66, 0x47, 0x40, 0x49, 0x02, 0x02, 0x07, 0xd0,
 };
 
+/* kPBES2 is a PKCS#12 file using PBES2 created with:
+ * openssl pkcs12 -export -inkey key.pem -in cert.pem -keypbe AES-128-CBC \
+ * -certpbe AES-128-CBC */
+static const uint8_t kPBES2[] = {
+  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,
+  0x04, 0x34, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07,
+  0x06, 0xa0, 0x82, 0x04, 0x25, 0x30, 0x82, 0x04, 0x21, 0x02, 0x01, 0x00,
+  0x30, 0x82, 0x04, 0x1a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x07, 0x01, 0x30, 0x49, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x05, 0x0d, 0x30, 0x3c, 0x30, 0x1b, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x0e, 0x04, 0x08, 0xdb,
+  0x48, 0xe6, 0x98, 0x09, 0x8f, 0x6e, 0x2d, 0x02, 0x02, 0x08, 0x00, 0x30,
+  0x1d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02,
+  0x04, 0x10, 0xee, 0xb3, 0x10, 0xe5, 0x21, 0x85, 0x03, 0x3e, 0x69, 0xad,
+  0xdf, 0x78, 0xa7, 0xd8, 0xac, 0xf1, 0x80, 0x82, 0x03, 0xc0, 0xcb, 0x58,
+  0x11, 0x28, 0x1d, 0xbc, 0x3c, 0x8c, 0xe7, 0x7b, 0x15, 0x67, 0x30, 0xf3,
+  0x2b, 0x94, 0x10, 0x8c, 0xbe, 0xfd, 0xaa, 0x11, 0xd7, 0x99, 0xee, 0x21,
+  0xb6, 0x1b, 0x4f, 0x53, 0xcb, 0x44, 0xff, 0x4f, 0xbf, 0xf6, 0x43, 0x3d,
+  0x12, 0xe6, 0x09, 0xe8, 0x05, 0xdd, 0x2f, 0xc5, 0x39, 0xde, 0x0c, 0x88,
+  0xe8, 0x4e, 0x89, 0x8f, 0x5f, 0xdf, 0x23, 0x50, 0xe6, 0xb7, 0xba, 0x1a,
+  0xdd, 0x1c, 0x63, 0x51, 0x0e, 0x71, 0xb7, 0xf7, 0x39, 0x3c, 0xd4, 0xe7,
+  0x52, 0x50, 0xc5, 0xd7, 0xbf, 0x65, 0x94, 0x72, 0x97, 0x2a, 0xb9, 0x68,
+  0xc2, 0xbd, 0x0c, 0x97, 0x02, 0x74, 0x23, 0x7f, 0x11, 0x6b, 0xea, 0xb4,
+  0xe4, 0x2f, 0xf0, 0x8b, 0x91, 0x5c, 0xdb, 0xae, 0x10, 0xbf, 0x89, 0xbc,
+  0x62, 0xef, 0x99, 0xbf, 0x07, 0x59, 0x58, 0x12, 0xef, 0xaf, 0xe6, 0xcd,
+  0x30, 0x27, 0xe4, 0xab, 0x44, 0xf7, 0xf9, 0x14, 0xb2, 0x5d, 0xfa, 0x97,
+  0xe6, 0x9a, 0xed, 0x85, 0x60, 0x86, 0xd9, 0xb0, 0xd7, 0xa4, 0xe4, 0x00,
+  0xa8, 0xee, 0xbb, 0xfc, 0x0d, 0xe8, 0x58, 0x7a, 0xca, 0x02, 0x1d, 0x02,
+  0xab, 0xbd, 0x16, 0x50, 0x4f, 0xfc, 0x60, 0xde, 0x48, 0xb1, 0x7f, 0xea,
+  0xba, 0x45, 0x7b, 0x29, 0xfe, 0x8e, 0xed, 0x48, 0xd2, 0x31, 0x64, 0xda,
+  0x89, 0x84, 0x6f, 0xd1, 0xd2, 0xb1, 0x7b, 0x97, 0x19, 0x38, 0x16, 0xd9,
+  0x3f, 0xd6, 0xdb, 0x6f, 0xab, 0x56, 0x34, 0xca, 0x34, 0x9c, 0x57, 0x41,
+  0x6e, 0x87, 0x85, 0x2a, 0xa8, 0xfb, 0xe9, 0xf6, 0x3d, 0xb6, 0x83, 0x7b,
+  0x02, 0xc9, 0xbe, 0xf1, 0xbb, 0x8e, 0xe5, 0x68, 0xae, 0xaa, 0xe1, 0x25,
+  0x8d, 0x1f, 0x1f, 0x52, 0x45, 0x3e, 0xef, 0x33, 0xd8, 0x58, 0xd9, 0x48,
+  0xd4, 0xb5, 0xe1, 0x53, 0x21, 0xb5, 0xbd, 0xd4, 0x63, 0x1f, 0xbf, 0xe4,
+  0x30, 0x5e, 0xc3, 0x63, 0xce, 0xdc, 0x12, 0x8c, 0xc7, 0x0c, 0xea, 0x3b,
+  0xf3, 0x0b, 0x38, 0x8d, 0xcc, 0x9b, 0xe7, 0xa0, 0x14, 0x5e, 0x48, 0x9c,
+  0x74, 0x86, 0x8e, 0x2b, 0x77, 0x80, 0xbb, 0x85, 0xa6, 0xd4, 0x25, 0x6e,
+  0x75, 0x07, 0x59, 0xd6, 0x88, 0x00, 0x35, 0x03, 0x5a, 0xb0, 0x86, 0x7e,
+  0x01, 0xa7, 0x77, 0x74, 0x13, 0xfa, 0x9f, 0x2d, 0xe3, 0x90, 0xda, 0x68,
+  0x23, 0x36, 0x0b, 0x62, 0x21, 0x76, 0xda, 0x6c, 0x05, 0x35, 0x80, 0xfc,
+  0xee, 0x5f, 0x3c, 0xac, 0x60, 0x2a, 0x9c, 0x6e, 0x4c, 0xaa, 0xa3, 0xd1,
+  0xdf, 0x2c, 0x7e, 0x0e, 0xc0, 0xa0, 0x84, 0xe4, 0xb2, 0x33, 0x1f, 0x8c,
+  0xcb, 0x74, 0x31, 0x18, 0x5b, 0x0b, 0x18, 0x41, 0xc6, 0x87, 0x13, 0xa2,
+  0xad, 0x1d, 0x43, 0x5e, 0x67, 0xd0, 0x31, 0xf5, 0x61, 0x7c, 0x3d, 0x16,
+  0x55, 0x01, 0x94, 0x45, 0xa4, 0x50, 0x0f, 0xb1, 0x1b, 0x81, 0x51, 0xa7,
+  0x92, 0xae, 0xa3, 0x6d, 0x4e, 0x55, 0x46, 0x37, 0x98, 0xe1, 0xe4, 0x5c,
+  0x29, 0x79, 0xc9, 0x76, 0x0a, 0xb5, 0x9d, 0x1b, 0x8a, 0xf6, 0xab, 0xeb,
+  0x69, 0x6e, 0x17, 0x88, 0xeb, 0x82, 0xfa, 0x78, 0x2f, 0x8c, 0x30, 0xfd,
+  0xf1, 0x74, 0xcd, 0x53, 0x78, 0x27, 0x43, 0x82, 0x05, 0x37, 0x07, 0xb3,
+  0x4c, 0x89, 0x9d, 0x00, 0x1d, 0x73, 0xad, 0x0f, 0xcd, 0x63, 0xbe, 0x9b,
+  0xa9, 0x50, 0xa5, 0x43, 0x74, 0x86, 0x87, 0xbc, 0xd9, 0x97, 0x66, 0x84,
+  0x35, 0x3e, 0x67, 0xce, 0x92, 0x2c, 0x78, 0xc7, 0x88, 0x19, 0x6a, 0x1c,
+  0xa8, 0x93, 0x0b, 0x79, 0x21, 0xe5, 0x39, 0x1b, 0x00, 0x68, 0x2a, 0x0b,
+  0xac, 0x6a, 0x2f, 0xc1, 0x9c, 0x90, 0x18, 0x86, 0x63, 0x53, 0x72, 0x34,
+  0xd9, 0xa8, 0x92, 0xce, 0x64, 0x3a, 0xeb, 0xba, 0xd8, 0x31, 0xf3, 0xfb,
+  0x2a, 0xac, 0xc6, 0xe7, 0xd1, 0x0b, 0x7c, 0xfc, 0xbb, 0x69, 0x57, 0xc8,
+  0x97, 0x3d, 0xdb, 0x81, 0x77, 0x2a, 0x9f, 0x07, 0x2c, 0x79, 0x69, 0xbc,
+  0x51, 0x0e, 0x68, 0x11, 0x00, 0x10, 0xed, 0x9f, 0xb8, 0x8d, 0xa0, 0x25,
+  0x20, 0xd3, 0x3d, 0x08, 0x20, 0x46, 0xfa, 0x89, 0xef, 0x69, 0x4c, 0x60,
+  0x33, 0x80, 0xb9, 0x53, 0xb4, 0x7b, 0xab, 0x38, 0xf1, 0xcd, 0xb8, 0x75,
+  0xc4, 0x85, 0x0a, 0xda, 0xab, 0x19, 0x40, 0xd3, 0x88, 0xd5, 0xf7, 0x5f,
+  0x8e, 0xcd, 0x8e, 0xa4, 0x1c, 0x9c, 0x22, 0x6d, 0xce, 0x66, 0x29, 0xfa,
+  0x62, 0x6f, 0x01, 0xdc, 0x46, 0x45, 0x38, 0x64, 0xf7, 0xc4, 0x94, 0xfd,
+  0x48, 0x44, 0x70, 0x4d, 0xef, 0xf0, 0x4b, 0x95, 0xf8, 0x68, 0x8d, 0xb7,
+  0x35, 0x7d, 0xc6, 0xf5, 0x97, 0xce, 0x5d, 0xad, 0xe8, 0x5c, 0xeb, 0x4f,
+  0x9b, 0x5b, 0x03, 0xce, 0x33, 0x60, 0xf5, 0xce, 0xcc, 0xfe, 0xfb, 0x77,
+  0x40, 0xc4, 0xf4, 0x9d, 0xf3, 0x2c, 0xdb, 0x83, 0xc2, 0x1a, 0xf2, 0xb6,
+  0xbe, 0xfc, 0x2c, 0x7f, 0x29, 0x20, 0x35, 0x50, 0x00, 0x60, 0x03, 0xd2,
+  0xb3, 0x03, 0x18, 0x64, 0xb9, 0x64, 0x98, 0x33, 0xdb, 0x47, 0x43, 0xe2,
+  0xa1, 0x85, 0x79, 0x9b, 0xb1, 0x0b, 0x0e, 0xbb, 0x14, 0x5f, 0xb9, 0x16,
+  0xb6, 0xc3, 0xf6, 0x5c, 0x01, 0xe3, 0xaa, 0x3f, 0x03, 0xad, 0x18, 0xeb,
+  0x0e, 0x3d, 0xa3, 0x1f, 0xcc, 0x4d, 0x48, 0x44, 0x7e, 0xda, 0xb9, 0x9d,
+  0x17, 0xe8, 0x92, 0x46, 0xea, 0xf5, 0x3e, 0x05, 0x4e, 0xa7, 0xb5, 0x94,
+  0x6d, 0x95, 0x42, 0xa7, 0x71, 0xfb, 0xc2, 0x45, 0xd6, 0xd2, 0x86, 0xd0,
+  0x79, 0x99, 0x1f, 0x96, 0x78, 0x22, 0xeb, 0x05, 0x26, 0xf2, 0xa1, 0x67,
+  0x67, 0x2b, 0xae, 0x1d, 0x28, 0x42, 0xd6, 0xbe, 0x08, 0xf6, 0xb7, 0x54,
+  0xc8, 0x82, 0xbf, 0x92, 0x0f, 0x2c, 0xba, 0x47, 0xe2, 0x01, 0x73, 0x2c,
+  0xd7, 0x34, 0x84, 0x2f, 0xb6, 0x41, 0x84, 0xeb, 0x7a, 0xb2, 0xf9, 0xdd,
+  0x31, 0xbe, 0x07, 0xb4, 0x88, 0x05, 0xd8, 0xe1, 0x79, 0x55, 0xe6, 0x4b,
+  0x8c, 0xdc, 0xd1, 0x76, 0x58, 0x72, 0x42, 0x28, 0xb3, 0x9f, 0xd0, 0x05,
+  0x37, 0x6b, 0x65, 0x74, 0xce, 0x0d, 0x01, 0xa9, 0x49, 0xc5, 0x90, 0xab,
+  0x90, 0x16, 0x2c, 0x9c, 0xba, 0xcb, 0x94, 0xc7, 0xfa, 0xe0, 0x39, 0x82,
+  0xa2, 0x88, 0xd6, 0x0c, 0xc4, 0x4d, 0xfe, 0xb4, 0xbc, 0x87, 0xe5, 0x63,
+  0x3b, 0x6b, 0xf0, 0xd1, 0x09, 0x39, 0x8f, 0x51, 0x4f, 0x32, 0xae, 0xed,
+  0x0c, 0xff, 0x79, 0x52, 0x19, 0xa9, 0x4e, 0x45, 0x11, 0xc3, 0x5f, 0xd6,
+  0x2b, 0x66, 0xe3, 0x9c, 0xbe, 0xbc, 0xda, 0x65, 0x25, 0xcd, 0xf5, 0x73,
+  0x45, 0x09, 0xf5, 0x5d, 0x6b, 0x83, 0x45, 0x28, 0x98, 0x2c, 0x58, 0x44,
+  0xca, 0x37, 0xeb, 0xc3, 0xc2, 0x10, 0x77, 0x14, 0x79, 0x9b, 0xd8, 0xb2,
+  0xbf, 0x45, 0xd5, 0x63, 0xe4, 0x37, 0x42, 0x7b, 0x2d, 0xe2, 0x49, 0xb3,
+  0x18, 0x8e, 0x86, 0x73, 0xf1, 0x59, 0x8a, 0xf2, 0x3c, 0x49, 0x12, 0x7b,
+  0xb1, 0x40, 0x8c, 0x8c, 0xac, 0x05, 0x50, 0xbd, 0x9b, 0x3b, 0x84, 0x81,
+  0x68, 0x26, 0x88, 0x1b, 0xbf, 0xa0, 0x28, 0xc2, 0x06, 0xa9, 0xe4, 0xd9,
+  0x1f, 0x5d, 0xca, 0x96, 0x4f, 0xfe, 0xd8, 0x64, 0xee, 0x73, 0x30, 0x82,
+  0x05, 0x76, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07,
+  0x01, 0xa0, 0x82, 0x05, 0x67, 0x04, 0x82, 0x05, 0x63, 0x30, 0x82, 0x05,
+  0x5f, 0x30, 0x82, 0x05, 0x5b, 0x06, 0x0b, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+  0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x02, 0xa0, 0x82, 0x05, 0x23, 0x30, 0x82,
+  0x05, 0x1f, 0x30, 0x49, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+  0x01, 0x05, 0x0d, 0x30, 0x3c, 0x30, 0x1b, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x0e, 0x04, 0x08, 0xe3, 0x3e,
+  0xd3, 0x8d, 0xd6, 0xb5, 0x8a, 0x05, 0x02, 0x02, 0x08, 0x00, 0x30, 0x1d,
+  0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02, 0x04,
+  0x10, 0x61, 0xa0, 0x2f, 0x8d, 0x0c, 0xa1, 0x03, 0xc9, 0xdf, 0x2e, 0x81,
+  0x65, 0xe0, 0x63, 0x70, 0x55, 0x04, 0x82, 0x04, 0xd0, 0x24, 0x1e, 0xf9,
+  0x1d, 0xc4, 0xe9, 0xbf, 0x49, 0x3c, 0x1e, 0x55, 0x4a, 0xd4, 0xb0, 0x0c,
+  0xdd, 0x5b, 0x92, 0xb2, 0xed, 0x18, 0xac, 0x66, 0x90, 0x1b, 0x29, 0x3d,
+  0x10, 0xad, 0x02, 0xe7, 0x17, 0x83, 0x44, 0x67, 0xba, 0x11, 0x6f, 0x05,
+  0xf5, 0xf7, 0x37, 0xcb, 0x5a, 0xe9, 0x0e, 0xc3, 0x4b, 0x1b, 0x62, 0xee,
+  0xb2, 0xb7, 0x14, 0x85, 0x07, 0x2d, 0x95, 0x83, 0xa9, 0xdc, 0x3d, 0x4b,
+  0x33, 0xad, 0x68, 0xbf, 0x54, 0xf8, 0xef, 0x25, 0x05, 0x40, 0xcd, 0x61,
+  0xbe, 0x12, 0xeb, 0x78, 0x75, 0x36, 0x08, 0x8c, 0x5a, 0x57, 0xa1, 0x98,
+  0xd5, 0x42, 0x01, 0x1b, 0x4c, 0x25, 0xc2, 0x18, 0x9f, 0x91, 0xfe, 0x78,
+  0x88, 0x99, 0x47, 0x5a, 0x20, 0x2c, 0x37, 0x31, 0x05, 0x98, 0xef, 0x91,
+  0x6e, 0xeb, 0x2e, 0x86, 0x90, 0x61, 0xb1, 0x57, 0x1a, 0x05, 0x82, 0x14,
+  0x0c, 0xa8, 0x94, 0xae, 0x56, 0x7b, 0xd6, 0x2f, 0x8b, 0x2e, 0x91, 0xa6,
+  0x12, 0x68, 0x1f, 0x06, 0x09, 0x2f, 0xa6, 0xed, 0x33, 0x99, 0x72, 0x56,
+  0xe5, 0xf7, 0xea, 0xcc, 0xcf, 0x27, 0xa5, 0xad, 0x49, 0x5a, 0xbc, 0x7b,
+  0xe3, 0x62, 0x63, 0x8f, 0x00, 0x2b, 0x96, 0xc5, 0x3f, 0xaf, 0x24, 0xba,
+  0xf6, 0x8d, 0xe2, 0xef, 0x18, 0x50, 0xd6, 0xd8, 0x4f, 0xb2, 0x5d, 0xb7,
+  0x96, 0x6f, 0x02, 0xf7, 0x7d, 0xf2, 0xa2, 0x7b, 0x9b, 0x13, 0x98, 0xde,
+  0xdd, 0x6e, 0xb5, 0x48, 0x52, 0x8e, 0x44, 0xad, 0xe0, 0xcf, 0x40, 0x9f,
+  0xfd, 0x88, 0x33, 0x66, 0xce, 0x6a, 0x49, 0x5f, 0xe7, 0x4b, 0x36, 0x93,
+  0x7f, 0x49, 0x62, 0xc9, 0x5a, 0xae, 0xa1, 0xca, 0xf7, 0x5a, 0xbe, 0x85,
+  0x77, 0x9a, 0x8f, 0xce, 0x4d, 0x84, 0x81, 0xd0, 0xa2, 0xee, 0x60, 0x92,
+  0x86, 0x16, 0x2a, 0xd5, 0x08, 0xb6, 0x58, 0x63, 0x07, 0x7c, 0x41, 0xac,
+  0x97, 0x4f, 0xf0, 0xcf, 0xd8, 0xd2, 0xb1, 0xd7, 0x1d, 0xe5, 0xb8, 0x7c,
+  0x04, 0x2b, 0xd9, 0xee, 0xf7, 0x22, 0x88, 0xa1, 0x53, 0xdb, 0x5e, 0x5b,
+  0x47, 0x49, 0xeb, 0xcf, 0x04, 0x78, 0x69, 0xd1, 0xfc, 0x8a, 0xa9, 0x61,
+  0x92, 0xbf, 0x5c, 0x7f, 0xde, 0x49, 0x42, 0xfc, 0x0d, 0xc2, 0xa2, 0x8f,
+  0xba, 0xdf, 0x12, 0xa4, 0x62, 0xfb, 0x8d, 0xd3, 0xc5, 0xf9, 0x85, 0x4c,
+  0x17, 0x70, 0xb7, 0xf7, 0x99, 0x29, 0x52, 0x92, 0x36, 0xc5, 0x4b, 0x31,
+  0x23, 0x5c, 0x09, 0x27, 0x3c, 0xa0, 0x76, 0x5d, 0x92, 0x99, 0x63, 0x88,
+  0xca, 0xad, 0xed, 0xd7, 0x85, 0x98, 0x2f, 0xbe, 0xaa, 0xa5, 0xf3, 0x0a,
+  0x76, 0x13, 0x01, 0x90, 0x8a, 0xe7, 0x5a, 0x2d, 0x2b, 0x1a, 0x80, 0x33,
+  0x86, 0xab, 0xd8, 0xa7, 0xae, 0x0b, 0x7d, 0xcd, 0x64, 0x8d, 0xa6, 0xb6,
+  0xfb, 0x83, 0x9f, 0x91, 0x23, 0xcb, 0xda, 0x63, 0xd0, 0xde, 0xf4, 0xdd,
+  0xaa, 0x23, 0x49, 0x6c, 0x44, 0xfa, 0x6f, 0x12, 0x13, 0x90, 0x37, 0xde,
+  0xa3, 0x72, 0x45, 0x1a, 0xa7, 0xab, 0x01, 0x6d, 0xd6, 0x34, 0xe7, 0x51,
+  0x0e, 0x33, 0xbc, 0x09, 0xbf, 0xb6, 0x16, 0xf8, 0xd3, 0x11, 0x11, 0xd1,
+  0x5f, 0xaa, 0x32, 0xb6, 0x5b, 0xe7, 0xbc, 0xdd, 0xaa, 0xe4, 0xed, 0x42,
+  0x3d, 0x2e, 0xf7, 0xa1, 0x06, 0x39, 0xd4, 0x00, 0xc6, 0xc8, 0xed, 0xb5,
+  0x96, 0xc1, 0xbf, 0x4c, 0xf1, 0xf6, 0xc6, 0x59, 0xf4, 0x99, 0x9c, 0x10,
+  0x22, 0xa1, 0x3a, 0xcd, 0x94, 0xac, 0x0b, 0xc8, 0x7e, 0x29, 0xbc, 0xf0,
+  0xae, 0x27, 0x7a, 0xb8, 0x5c, 0xa0, 0x13, 0x36, 0xb5, 0x19, 0x4b, 0x2c,
+  0xc1, 0xce, 0x49, 0x57, 0x1d, 0x36, 0xf0, 0xc2, 0x4c, 0xdf, 0x6d, 0xc9,
+  0x64, 0x68, 0xcb, 0xea, 0x22, 0x32, 0xd7, 0x11, 0x2c, 0x77, 0xbe, 0x01,
+  0xa3, 0x82, 0x2d, 0xa1, 0x4b, 0x13, 0x93, 0x87, 0x3d, 0x01, 0x74, 0xc6,
+  0xc6, 0xf9, 0xae, 0x2e, 0xa1, 0x44, 0x5d, 0x47, 0x6c, 0x6f, 0xc6, 0xce,
+  0xef, 0x32, 0xf8, 0x8d, 0x53, 0x4d, 0xa5, 0xf0, 0xa0, 0x51, 0x7e, 0xd8,
+  0x35, 0x55, 0x2a, 0x04, 0xb9, 0x42, 0xa7, 0x51, 0xba, 0xad, 0xce, 0x88,
+  0x7b, 0x93, 0x25, 0x9d, 0x03, 0x08, 0xfa, 0x75, 0x38, 0x63, 0x78, 0x13,
+  0x11, 0x9d, 0xf6, 0xcc, 0x18, 0xe3, 0x99, 0xa9, 0x5d, 0x90, 0x6b, 0xbf,
+  0x9c, 0x69, 0x99, 0x63, 0x27, 0x35, 0x8a, 0x26, 0x07, 0x67, 0xd1, 0xae,
+  0x57, 0xec, 0xc0, 0x45, 0x6e, 0x2a, 0x42, 0x46, 0x8f, 0xe4, 0x84, 0xc7,
+  0x67, 0x06, 0x0c, 0xa7, 0x7e, 0x5c, 0x20, 0x80, 0xdc, 0xc1, 0xe4, 0x7a,
+  0x74, 0x76, 0x8f, 0x41, 0x78, 0xce, 0x6a, 0xf9, 0xcb, 0x7f, 0xe9, 0x17,
+  0x70, 0x45, 0x01, 0x9a, 0xc3, 0x9c, 0xa2, 0x68, 0xa0, 0x79, 0xfd, 0x44,
+  0x4c, 0xc8, 0xa0, 0xaf, 0xa5, 0xba, 0x0f, 0x03, 0x30, 0x43, 0x4a, 0x1d,
+  0x3e, 0xd4, 0x8e, 0x1f, 0x6d, 0x09, 0xf9, 0x63, 0xde, 0xd2, 0x9e, 0x77,
+  0xe7, 0xde, 0x61, 0x52, 0x76, 0x0f, 0x6d, 0x37, 0xf7, 0xc2, 0x69, 0x96,
+  0x9d, 0xc5, 0xd9, 0x15, 0x10, 0xf2, 0x22, 0x1f, 0x3b, 0x83, 0xb3, 0xb4,
+  0x2c, 0x25, 0x36, 0xc3, 0x3a, 0x24, 0x17, 0xed, 0xad, 0x11, 0x1f, 0x46,
+  0x31, 0x0c, 0x6a, 0x3c, 0xd2, 0x1a, 0xe7, 0x41, 0xb3, 0x75, 0xd8, 0x80,
+  0xb3, 0xf8, 0x2b, 0xab, 0xb5, 0x81, 0xc6, 0x5e, 0x40, 0x9a, 0x77, 0xaa,
+  0x79, 0x31, 0x1f, 0x79, 0xfe, 0x0f, 0x0f, 0xb0, 0x36, 0xb7, 0xdc, 0xca,
+  0xf6, 0xbf, 0x80, 0xeb, 0x78, 0xc6, 0x73, 0x6a, 0xb3, 0x71, 0x69, 0x9c,
+  0x1d, 0xdd, 0x90, 0xd9, 0x73, 0x07, 0x43, 0x37, 0x19, 0x7f, 0x22, 0xa4,
+  0x9a, 0x4d, 0x98, 0x66, 0x10, 0x5b, 0x08, 0x62, 0xb3, 0xd8, 0x2f, 0x56,
+  0x68, 0x22, 0xdf, 0xd1, 0xa2, 0x5a, 0x45, 0xf9, 0xb4, 0xb9, 0xf2, 0x48,
+  0x4e, 0x38, 0x1a, 0x23, 0x36, 0x6d, 0x42, 0x56, 0xbb, 0x32, 0xe3, 0x00,
+  0x84, 0xa9, 0xe2, 0xba, 0xb6, 0x86, 0xc9, 0xa6, 0x64, 0x8a, 0xd6, 0xa6,
+  0xc4, 0xd7, 0x3e, 0x8b, 0x34, 0x1b, 0x6b, 0x65, 0xfe, 0xb1, 0xc9, 0x93,
+  0xe1, 0xeb, 0x8a, 0x3b, 0xf1, 0x0f, 0xdb, 0x84, 0xe2, 0x2d, 0xf8, 0x69,
+  0x04, 0xee, 0xaf, 0x58, 0x2f, 0xc7, 0x96, 0x70, 0x4d, 0xd9, 0x4c, 0x1d,
+  0x52, 0x38, 0xc6, 0x26, 0x27, 0x41, 0x38, 0x0b, 0xa5, 0x1c, 0x16, 0xd0,
+  0x1d, 0x32, 0x99, 0xb9, 0x1f, 0x35, 0xaf, 0x02, 0xb0, 0x13, 0x0f, 0x95,
+  0xd3, 0x9b, 0xd6, 0x09, 0xcc, 0x29, 0x46, 0xe8, 0xf1, 0x54, 0x4d, 0xb8,
+  0x96, 0xa6, 0x0d, 0x59, 0x61, 0x1f, 0xee, 0xaf, 0xbc, 0x23, 0x58, 0xff,
+  0xcf, 0x96, 0x91, 0x1f, 0x00, 0x80, 0x4e, 0x9a, 0xa2, 0xe0, 0x00, 0xf7,
+  0x3e, 0xb1, 0x91, 0x6c, 0x29, 0x58, 0x5e, 0xe7, 0xc7, 0x23, 0xfa, 0x88,
+  0xf7, 0xfb, 0x0b, 0x0e, 0x4a, 0x04, 0x46, 0xe0, 0x67, 0x10, 0x09, 0xea,
+  0xc0, 0xa9, 0xbe, 0x83, 0x11, 0x33, 0x8e, 0xfb, 0xd6, 0xd5, 0x67, 0xef,
+  0xb4, 0x13, 0x4d, 0x17, 0xa1, 0x44, 0xb7, 0x98, 0x77, 0xd0, 0x63, 0xe7,
+  0x9c, 0xa7, 0x96, 0x29, 0xe5, 0xfe, 0x72, 0x4c, 0xa9, 0x85, 0x9b, 0xc9,
+  0xf3, 0xf6, 0x05, 0x0a, 0x28, 0x68, 0x99, 0x31, 0xe8, 0x64, 0x30, 0x9c,
+  0x2a, 0x90, 0x48, 0x84, 0x00, 0x1a, 0x66, 0x0e, 0x3e, 0xf7, 0xaa, 0xc9,
+  0x6c, 0x5b, 0x57, 0x7b, 0xa9, 0x17, 0x91, 0x1e, 0x6b, 0xe8, 0x12, 0xa1,
+  0xd4, 0xde, 0x1e, 0x38, 0x14, 0x7b, 0xe0, 0x9a, 0x15, 0xae, 0x5a, 0x26,
+  0x93, 0x7a, 0xd6, 0x8d, 0x26, 0x61, 0x28, 0xf2, 0x40, 0x71, 0xc7, 0x8a,
+  0x2d, 0x69, 0x72, 0x04, 0x5b, 0xb9, 0xc1, 0x7b, 0x17, 0xde, 0x2c, 0xfc,
+  0xa9, 0xf2, 0xf8, 0x34, 0x33, 0x09, 0x87, 0x91, 0xdf, 0xeb, 0xf7, 0x57,
+  0x5b, 0x32, 0xe2, 0xd4, 0xe4, 0x47, 0x78, 0xe8, 0x9b, 0x1a, 0xab, 0x44,
+  0x55, 0x28, 0x98, 0x20, 0xa7, 0x16, 0x8b, 0x4e, 0x42, 0xf1, 0x91, 0xbe,
+  0x00, 0x87, 0x3a, 0x91, 0x63, 0x9a, 0xc2, 0x8d, 0x13, 0x34, 0x8b, 0x33,
+  0x02, 0x88, 0x1e, 0xb1, 0xa8, 0x07, 0x6d, 0xb1, 0xf5, 0xb3, 0x7a, 0x3d,
+  0x17, 0x3f, 0xbd, 0xa1, 0xdb, 0x04, 0x0f, 0x29, 0x7b, 0x0e, 0x98, 0x18,
+  0x63, 0x0b, 0x60, 0xcd, 0xa5, 0x0d, 0x5f, 0x1e, 0x53, 0xcd, 0xfa, 0xc0,
+  0xc7, 0x99, 0x53, 0x5f, 0xb7, 0xe5, 0x4a, 0x30, 0xde, 0x14, 0xc9, 0x49,
+  0x46, 0x31, 0xb6, 0x92, 0xf3, 0x4b, 0xc1, 0xb0, 0xdd, 0xec, 0x48, 0xff,
+  0x2d, 0x52, 0x53, 0x64, 0x27, 0x4c, 0x78, 0x96, 0x80, 0x90, 0xa3, 0xd7,
+  0xfd, 0x7a, 0x23, 0x36, 0xa0, 0x76, 0x9e, 0x96, 0xfc, 0xcd, 0xec, 0x58,
+  0xf8, 0x76, 0x4b, 0x2f, 0x8d, 0xb9, 0xd6, 0x89, 0xa1, 0x57, 0xe1, 0xc6,
+  0xed, 0x9a, 0x1e, 0xde, 0xc7, 0x68, 0x93, 0x2b, 0x2e, 0x84, 0x1a, 0xf9,
+  0x8c, 0x58, 0xb8, 0xf0, 0x29, 0xfe, 0x7b, 0x03, 0x84, 0xe8, 0x52, 0x1c,
+  0x01, 0xbb, 0xcc, 0x5d, 0x88, 0xcd, 0x37, 0x8b, 0xe2, 0x2d, 0x30, 0xd1,
+  0xbe, 0xf7, 0xc1, 0x95, 0xb7, 0x01, 0x43, 0xab, 0x30, 0x3f, 0x96, 0x47,
+  0x6d, 0x52, 0x29, 0x87, 0x10, 0x31, 0x25, 0x30, 0x23, 0x06, 0x09, 0x2a,
+  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15, 0x31, 0x16, 0x04, 0x14,
+  0x14, 0x74, 0x2d, 0x52, 0x8e, 0x0d, 0x0c, 0x06, 0x6c, 0x32, 0x64, 0xd3,
+  0x7e, 0x33, 0x31, 0x68, 0x8b, 0x28, 0x1a, 0x75, 0x30, 0x31, 0x30, 0x21,
+  0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04,
+  0x14, 0x2f, 0x5c, 0xc6, 0xaf, 0xa7, 0xcc, 0xb5, 0x77, 0x40, 0xca, 0x71,
+  0xc3, 0x8c, 0xc6, 0x69, 0xdc, 0xc6, 0x7f, 0x54, 0xef, 0x04, 0x08, 0xf8,
+  0x9c, 0x8b, 0x12, 0x27, 0xe8, 0xec, 0x65, 0x02, 0x02, 0x08, 0x00};
+
 static const char kPassword[] = "foo";
 
 static bool Test(const char *name, const uint8_t *der, size_t der_len) {
@@ -759,6 +978,7 @@
   if (!Test("OpenSSL", kOpenSSL, sizeof(kOpenSSL)) ||
       !Test("NSS", kNSS, sizeof(kNSS)) ||
       !Test("Windows", kWindows, sizeof(kWindows)) ||
+      !Test("PBES2", kPBES2, sizeof(kPBES2)) ||
       !TestCompat(kWindows, sizeof(kWindows))) {
     return 1;
   }
diff --git a/src/crypto/pkcs8/pkcs8.c b/src/crypto/pkcs8/pkcs8.c
index 64a2d02..08cc5a3 100644
--- a/src/crypto/pkcs8/pkcs8.c
+++ b/src/crypto/pkcs8/pkcs8.c
@@ -59,27 +59,18 @@
 #include <limits.h>
 #include <string.h>
 
-#include <openssl/asn1.h>
-#include <openssl/buf.h>
 #include <openssl/bytestring.h>
 #include <openssl/cipher.h>
 #include <openssl/digest.h>
 #include <openssl/err.h>
-#include <openssl/hmac.h>
 #include <openssl/mem.h>
-#include <openssl/obj.h>
+#include <openssl/nid.h>
 #include <openssl/rand.h>
-#include <openssl/x509.h>
 
 #include "internal.h"
 #include "../internal.h"
-#include "../bytestring/internal.h"
 
 
-#define PKCS12_KEY_ID 1
-#define PKCS12_IV_ID 2
-#define PKCS12_MAC_ID 3
-
 static int ascii_to_ucs2(const char *ascii, size_t ascii_len,
                          uint8_t **out, size_t *out_len) {
   size_t ulen = ascii_len * 2 + 2;
@@ -89,6 +80,7 @@
 
   uint8_t *unitmp = OPENSSL_malloc(ulen);
   if (unitmp == NULL) {
+    OPENSSL_PUT_ERROR(PKCS8, ERR_R_MALLOC_FAILURE);
     return 0;
   }
   for (size_t i = 0; i < ulen - 2; i += 2) {
@@ -104,11 +96,9 @@
   return 1;
 }
 
-static int pkcs12_key_gen_raw(const uint8_t *pass_raw, size_t pass_raw_len,
-                              const uint8_t *salt, size_t salt_len,
-                              uint8_t id, unsigned iterations,
-                              size_t out_len, uint8_t *out,
-                              const EVP_MD *md) {
+int pkcs12_key_gen(const char *pass, size_t pass_len, const uint8_t *salt,
+                   size_t salt_len, uint8_t id, unsigned iterations,
+                   size_t out_len, uint8_t *out, const EVP_MD *md) {
   /* See https://tools.ietf.org/html/rfc7292#appendix-B. Quoted parts of the
    * specification have errata applied and other typos fixed. */
 
@@ -117,6 +107,18 @@
     return 0;
   }
 
+  int ret = 0;
+  EVP_MD_CTX ctx;
+  EVP_MD_CTX_init(&ctx);
+  uint8_t *pass_raw = NULL, *I = NULL;
+  size_t pass_raw_len = 0, I_len = 0;
+  /* If |pass| is NULL, we use the empty string rather than {0, 0} as the raw
+   * password. */
+  if (pass != NULL &&
+      !ascii_to_ucs2(pass, pass_len, &pass_raw, &pass_raw_len)) {
+    goto err;
+  }
+
   /* In the spec, |block_size| is called "v", but measured in bits. */
   size_t block_size = EVP_MD_block_size(md);
 
@@ -138,20 +140,20 @@
   if (salt_len + block_size - 1 < salt_len ||
       pass_raw_len + block_size - 1 < pass_raw_len) {
     OPENSSL_PUT_ERROR(PKCS8, ERR_R_OVERFLOW);
-    return 0;
+    goto err;
   }
   size_t S_len = block_size * ((salt_len + block_size - 1) / block_size);
   size_t P_len = block_size * ((pass_raw_len + block_size - 1) / block_size);
-  size_t I_len = S_len + P_len;
+  I_len = S_len + P_len;
   if (I_len < S_len) {
     OPENSSL_PUT_ERROR(PKCS8, ERR_R_OVERFLOW);
-    return 0;
+    goto err;
   }
 
-  uint8_t *I = OPENSSL_malloc(I_len);
+  I = OPENSSL_malloc(I_len);
   if (I_len != 0 && I == NULL) {
     OPENSSL_PUT_ERROR(PKCS8, ERR_R_MALLOC_FAILURE);
-    return 0;
+    goto err;
   }
 
   for (size_t i = 0; i < S_len; i++) {
@@ -161,10 +163,6 @@
     I[i + S_len] = pass_raw[i % pass_raw_len];
   }
 
-  int ret = 0;
-  EVP_MD_CTX ctx;
-  EVP_MD_CTX_init(&ctx);
-
   while (out_len != 0) {
     /* A. Set A_i=H^r(D||I). (i.e., the r-th hash of D||I,
      * H(H(H(... H(D||I)))) */
@@ -216,32 +214,32 @@
   ret = 1;
 
 err:
-  OPENSSL_cleanse(I, I_len);
-  OPENSSL_free(I);
+  if (I != NULL) {
+    OPENSSL_cleanse(I, I_len);
+    OPENSSL_free(I);
+  }
+  if (pass_raw != NULL) {
+    OPENSSL_cleanse(pass_raw, pass_raw_len);
+    OPENSSL_free(pass_raw);
+  }
   EVP_MD_CTX_cleanup(&ctx);
   return ret;
 }
 
 static int pkcs12_pbe_cipher_init(const struct pbe_suite *suite,
                                   EVP_CIPHER_CTX *ctx, unsigned iterations,
-                                  const uint8_t *pass_raw, size_t pass_raw_len,
+                                  const char *pass, size_t pass_len,
                                   const uint8_t *salt, size_t salt_len,
                                   int is_encrypt) {
   const EVP_CIPHER *cipher = suite->cipher_func();
   const EVP_MD *md = suite->md_func();
 
   uint8_t key[EVP_MAX_KEY_LENGTH];
-  if (!pkcs12_key_gen_raw(pass_raw, pass_raw_len, salt,
-                          salt_len, PKCS12_KEY_ID, iterations,
-                          EVP_CIPHER_key_length(cipher), key, md)) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_KEY_GEN_ERROR);
-    return 0;
-  }
-
   uint8_t iv[EVP_MAX_IV_LENGTH];
-  if (!pkcs12_key_gen_raw(pass_raw, pass_raw_len, salt,
-                          salt_len, PKCS12_IV_ID, iterations,
-                          EVP_CIPHER_iv_length(cipher), iv, md)) {
+  if (!pkcs12_key_gen(pass, pass_len, salt, salt_len, PKCS12_KEY_ID, iterations,
+                      EVP_CIPHER_key_length(cipher), key, md) ||
+      !pkcs12_key_gen(pass, pass_len, salt, salt_len, PKCS12_IV_ID, iterations,
+                      EVP_CIPHER_iv_length(cipher), iv, md)) {
     OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_KEY_GEN_ERROR);
     return 0;
   }
@@ -253,8 +251,8 @@
 }
 
 static int pkcs12_pbe_decrypt_init(const struct pbe_suite *suite,
-                                   EVP_CIPHER_CTX *ctx, const uint8_t *pass_raw,
-                                   size_t pass_raw_len, CBS *param) {
+                                   EVP_CIPHER_CTX *ctx, const char *pass,
+                                   size_t pass_len, CBS *param) {
   CBS pbe_param, salt;
   uint64_t iterations;
   if (!CBS_get_asn1(param, &pbe_param, CBS_ASN1_SEQUENCE) ||
@@ -271,32 +269,52 @@
     return 0;
   }
 
-  return pkcs12_pbe_cipher_init(suite, ctx, (unsigned)iterations, pass_raw,
-                                pass_raw_len, CBS_data(&salt), CBS_len(&salt),
+  return pkcs12_pbe_cipher_init(suite, ctx, (unsigned)iterations, pass,
+                                pass_len, CBS_data(&salt), CBS_len(&salt),
                                 0 /* decrypt */);
 }
 
 static const struct pbe_suite kBuiltinPBE[] = {
     {
-        NID_pbe_WithSHA1And40BitRC2_CBC, EVP_rc2_40_cbc, EVP_sha1,
-        pkcs12_pbe_decrypt_init, PBE_UCS2_CONVERT_PASSWORD,
+        NID_pbe_WithSHA1And40BitRC2_CBC,
+        /* 1.2.840.113549.1.12.1.6 */
+        {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x06},
+        10,
+        EVP_rc2_40_cbc,
+        EVP_sha1,
+        pkcs12_pbe_decrypt_init,
     },
     {
-        NID_pbe_WithSHA1And128BitRC4, EVP_rc4, EVP_sha1,
-        pkcs12_pbe_decrypt_init, PBE_UCS2_CONVERT_PASSWORD,
+        NID_pbe_WithSHA1And128BitRC4,
+        /* 1.2.840.113549.1.12.1.1 */
+        {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x01},
+        10,
+        EVP_rc4,
+        EVP_sha1,
+        pkcs12_pbe_decrypt_init,
     },
     {
-        NID_pbe_WithSHA1And3_Key_TripleDES_CBC, EVP_des_ede3_cbc, EVP_sha1,
-        pkcs12_pbe_decrypt_init, PBE_UCS2_CONVERT_PASSWORD,
+        NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
+        /* 1.2.840.113549.1.12.1.3 */
+        {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01, 0x03},
+        10,
+        EVP_des_ede3_cbc,
+        EVP_sha1,
+        pkcs12_pbe_decrypt_init,
     },
     {
-        NID_pbes2, NULL, NULL, PKCS5_pbe2_decrypt_init, 0,
+        NID_pbes2,
+        /* 1.2.840.113549.1.5.13 */
+        {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0d},
+        9,
+        NULL,
+        NULL,
+        PKCS5_pbe2_decrypt_init,
     },
 };
 
 static const struct pbe_suite *get_pbe_suite(int pbe_nid) {
-  unsigned i;
-  for (i = 0; i < OPENSSL_ARRAY_SIZE(kBuiltinPBE); i++) {
+  for (unsigned i = 0; i < OPENSSL_ARRAY_SIZE(kBuiltinPBE); i++) {
     if (kBuiltinPBE[i].pbe_nid == pbe_nid) {
       return &kBuiltinPBE[i];
     }
@@ -305,51 +323,9 @@
   return NULL;
 }
 
-/* pass_to_pass_raw performs a password conversion (possibly a no-op)
- * appropriate to the supplied |pbe_nid|. The input |pass| is treated as a
- * NUL-terminated string if |pass_len| is -1, otherwise it is treated as a
- * buffer of the specified length. If the supplied PBE NID sets the
- * |PBE_UCS2_CONVERT_PASSWORD| flag, the supplied |pass| will be converted to
- * UCS-2.
- *
- * It sets |*out_pass_raw| to a new buffer that must be freed by the caller. It
- * returns one on success and zero on error. */
-static int pass_to_pass_raw(int pbe_nid, const char *pass, int pass_len,
-                            uint8_t **out_pass_raw, size_t *out_pass_raw_len) {
-  if (pass == NULL) {
-    *out_pass_raw = NULL;
-    *out_pass_raw_len = 0;
-    return 1;
-  }
-
-  if (pass_len == -1) {
-    pass_len = strlen(pass);
-  } else if (pass_len < 0 || pass_len > 2000000000) {
-    OPENSSL_PUT_ERROR(PKCS8, ERR_R_OVERFLOW);
-    return 0;
-  }
-
-  const struct pbe_suite *suite = get_pbe_suite(pbe_nid);
-  if (suite != NULL && (suite->flags & PBE_UCS2_CONVERT_PASSWORD)) {
-    if (!ascii_to_ucs2(pass, pass_len, out_pass_raw, out_pass_raw_len)) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
-      return 0;
-    }
-  } else {
-    *out_pass_raw = BUF_memdup(pass, pass_len);
-    if (*out_pass_raw == NULL) {
-      OPENSSL_PUT_ERROR(PKCS8, ERR_R_MALLOC_FAILURE);
-      return 0;
-    }
-    *out_pass_raw_len = (size_t)pass_len;
-  }
-
-  return 1;
-}
-
 static int pkcs12_pbe_encrypt_init(CBB *out, EVP_CIPHER_CTX *ctx, int alg,
-                                   unsigned iterations, const uint8_t *pass_raw,
-                                   size_t pass_raw_len, const uint8_t *salt,
+                                   unsigned iterations, const char *pass,
+                                   size_t pass_len, const uint8_t *salt,
                                    size_t salt_len) {
   const struct pbe_suite *suite = get_pbe_suite(alg);
   if (suite == NULL) {
@@ -358,9 +334,10 @@
   }
 
   /* See RFC 2898, appendix A.3. */
-  CBB algorithm, param, salt_cbb;
+  CBB algorithm, oid, param, salt_cbb;
   if (!CBB_add_asn1(out, &algorithm, CBS_ASN1_SEQUENCE) ||
-      !OBJ_nid2cbb(&algorithm, alg) ||
+      !CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) ||
+      !CBB_add_bytes(&oid, suite->oid, suite->oid_len) ||
       !CBB_add_asn1(&algorithm, &param, CBS_ASN1_SEQUENCE) ||
       !CBB_add_asn1(&param, &salt_cbb, CBS_ASN1_OCTETSTRING) ||
       !CBB_add_bytes(&salt_cbb, salt, salt_len) ||
@@ -369,13 +346,13 @@
     return 0;
   }
 
-  return pkcs12_pbe_cipher_init(suite, ctx, iterations, pass_raw, pass_raw_len,
-                                salt, salt_len, 1 /* encrypt */);
+  return pkcs12_pbe_cipher_init(suite, ctx, iterations, pass, pass_len, salt,
+                                salt_len, 1 /* encrypt */);
 }
 
-static int pbe_decrypt(uint8_t **out, size_t *out_len, CBS *algorithm,
-                       const uint8_t *pass_raw, size_t pass_raw_len,
-                       const uint8_t *in, size_t in_len) {
+int pkcs8_pbe_decrypt(uint8_t **out, size_t *out_len, CBS *algorithm,
+                      const char *pass, size_t pass_len, const uint8_t *in,
+                      size_t in_len) {
   int ret = 0;
   uint8_t *buf = NULL;;
   EVP_CIPHER_CTX ctx;
@@ -387,13 +364,19 @@
     goto err;
   }
 
-  const struct pbe_suite *suite = get_pbe_suite(OBJ_cbs2nid(&obj));
+  const struct pbe_suite *suite = NULL;
+  for (unsigned i = 0; i < OPENSSL_ARRAY_SIZE(kBuiltinPBE); i++) {
+    if (CBS_mem_equal(&obj, kBuiltinPBE[i].oid, kBuiltinPBE[i].oid_len)) {
+      suite = &kBuiltinPBE[i];
+      break;
+    }
+  }
   if (suite == NULL) {
     OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_UNKNOWN_ALGORITHM);
     goto err;
   }
 
-  if (!suite->decrypt_init(suite, &ctx, pass_raw, pass_raw_len, algorithm)) {
+  if (!suite->decrypt_init(suite, &ctx, pass, pass_len, algorithm)) {
     OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_KEYGEN_FAILURE);
     goto err;
   }
@@ -426,86 +409,41 @@
   return ret;
 }
 
-static PKCS8_PRIV_KEY_INFO *pkcs8_decrypt_raw(X509_SIG *pkcs8,
-                                              const uint8_t *pass_raw,
-                                              size_t pass_raw_len) {
-  PKCS8_PRIV_KEY_INFO *ret = NULL;
-  uint8_t *in = NULL, *out = NULL;
-  size_t out_len = 0;
-
-  /* Convert the legacy ASN.1 object to a byte string. */
-  int in_len = i2d_X509_SIG(pkcs8, &in);
-  if (in_len < 0) {
-    goto err;
-  }
-
+EVP_PKEY *PKCS8_parse_encrypted_private_key(CBS *cbs, const char *pass,
+                                            size_t pass_len) {
   /* See RFC 5208, section 6. */
-  CBS cbs, epki, algorithm, ciphertext;
-  CBS_init(&cbs, in, in_len);
-  if (!CBS_get_asn1(&cbs, &epki, CBS_ASN1_SEQUENCE) ||
+  CBS epki, algorithm, ciphertext;
+  if (!CBS_get_asn1(cbs, &epki, CBS_ASN1_SEQUENCE) ||
       !CBS_get_asn1(&epki, &algorithm, CBS_ASN1_SEQUENCE) ||
       !CBS_get_asn1(&epki, &ciphertext, CBS_ASN1_OCTETSTRING) ||
-      CBS_len(&epki) != 0 ||
-      CBS_len(&cbs) != 0) {
+      CBS_len(&epki) != 0) {
     OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
-    goto err;
+    return 0;
   }
 
-  if (!pbe_decrypt(&out, &out_len, &algorithm, pass_raw, pass_raw_len,
-                   CBS_data(&ciphertext), CBS_len(&ciphertext))) {
-    goto err;
+  uint8_t *out;
+  size_t out_len;
+  if (!pkcs8_pbe_decrypt(&out, &out_len, &algorithm, pass, pass_len,
+                         CBS_data(&ciphertext), CBS_len(&ciphertext))) {
+    return 0;
   }
 
-  if (out_len > LONG_MAX) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
-    goto err;
-  }
-
-  /* Convert back to legacy ASN.1 objects. */
-  const uint8_t *ptr = out;
-  ret = d2i_PKCS8_PRIV_KEY_INFO(NULL, &ptr, (long)out_len);
-  OPENSSL_cleanse(out, out_len);
-  if (ret == NULL || ptr != out + out_len) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
-    PKCS8_PRIV_KEY_INFO_free(ret);
-    ret = NULL;
-  }
-
-err:
-  OPENSSL_free(in);
+  CBS pki;
+  CBS_init(&pki, out, out_len);
+  EVP_PKEY *ret = EVP_parse_private_key(&pki);
   OPENSSL_cleanse(out, out_len);
   OPENSSL_free(out);
   return ret;
 }
 
-PKCS8_PRIV_KEY_INFO *PKCS8_decrypt(X509_SIG *pkcs8, const char *pass,
-                                   int pass_len) {
-  uint8_t *pass_raw = NULL;
-  size_t pass_raw_len = 0;
-  if (!pass_to_pass_raw(OBJ_obj2nid(pkcs8->algor->algorithm), pass, pass_len,
-                        &pass_raw, &pass_raw_len)) {
-    return NULL;
-  }
-
-  PKCS8_PRIV_KEY_INFO *ret = pkcs8_decrypt_raw(pkcs8, pass_raw, pass_raw_len);
-
-  if (pass_raw) {
-    OPENSSL_cleanse(pass_raw, pass_raw_len);
-    OPENSSL_free(pass_raw);
-  }
-  return ret;
-}
-
-static X509_SIG *pkcs8_encrypt_raw(int pbe_nid, const EVP_CIPHER *cipher,
-                                   const uint8_t *pass_raw, size_t pass_raw_len,
-                                   const uint8_t *salt, size_t salt_len,
-                                   int iterations, PKCS8_PRIV_KEY_INFO *p8inf) {
-  X509_SIG *ret = NULL;
-  uint8_t *plaintext = NULL, *salt_buf = NULL, *der = NULL;
-  int plaintext_len = -1;
-  size_t der_len;
-  CBB cbb;
-  CBB_zero(&cbb);
+int PKCS8_marshal_encrypted_private_key(CBB *out, int pbe_nid,
+                                        const EVP_CIPHER *cipher,
+                                        const char *pass, size_t pass_len,
+                                        const uint8_t *salt, size_t salt_len,
+                                        int iterations, const EVP_PKEY *pkey) {
+  int ret = 0;
+  uint8_t *plaintext = NULL, *salt_buf = NULL;
+  size_t plaintext_len = 0;
   EVP_CIPHER_CTX ctx;
   EVP_CIPHER_CTX_init(&ctx);
 
@@ -528,699 +466,58 @@
     iterations = PKCS5_DEFAULT_ITERATIONS;
   }
 
-  /* Convert the input from the legacy ASN.1 format. */
-  plaintext_len = i2d_PKCS8_PRIV_KEY_INFO(p8inf, &plaintext);
-  if (plaintext_len < 0) {
+  /* Serialize the input key. */
+  CBB plaintext_cbb;
+  if (!CBB_init(&plaintext_cbb, 128) ||
+      !EVP_marshal_private_key(&plaintext_cbb, pkey) ||
+      !CBB_finish(&plaintext_cbb, &plaintext, &plaintext_len)) {
+    CBB_cleanup(&plaintext_cbb);
     goto err;
   }
 
   CBB epki;
-  if (!CBB_init(&cbb, 128) ||
-      !CBB_add_asn1(&cbb, &epki, CBS_ASN1_SEQUENCE)) {
+  if (!CBB_add_asn1(out, &epki, CBS_ASN1_SEQUENCE)) {
     goto err;
   }
 
   int alg_ok;
   if (pbe_nid == -1) {
     alg_ok = PKCS5_pbe2_encrypt_init(&epki, &ctx, cipher, (unsigned)iterations,
-                                     pass_raw, pass_raw_len, salt, salt_len);
+                                     pass, pass_len, salt, salt_len);
   } else {
     alg_ok = pkcs12_pbe_encrypt_init(&epki, &ctx, pbe_nid, (unsigned)iterations,
-                                     pass_raw, pass_raw_len, salt, salt_len);
+                                     pass, pass_len, salt, salt_len);
   }
   if (!alg_ok) {
     goto err;
   }
 
-  size_t max_out = (size_t)plaintext_len + EVP_CIPHER_CTX_block_size(&ctx);
-  if (max_out < (size_t)plaintext_len) {
+  size_t max_out = plaintext_len + EVP_CIPHER_CTX_block_size(&ctx);
+  if (max_out < plaintext_len) {
     OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_TOO_LONG);
     goto err;
   }
 
   CBB ciphertext;
-  uint8_t *out;
+  uint8_t *ptr;
   int n1, n2;
   if (!CBB_add_asn1(&epki, &ciphertext, CBS_ASN1_OCTETSTRING) ||
-      !CBB_reserve(&ciphertext, &out, max_out) ||
-      !EVP_CipherUpdate(&ctx, out, &n1, plaintext, plaintext_len) ||
-      !EVP_CipherFinal_ex(&ctx, out + n1, &n2) ||
+      !CBB_reserve(&ciphertext, &ptr, max_out) ||
+      !EVP_CipherUpdate(&ctx, ptr, &n1, plaintext, plaintext_len) ||
+      !EVP_CipherFinal_ex(&ctx, ptr + n1, &n2) ||
       !CBB_did_write(&ciphertext, n1 + n2) ||
-      !CBB_finish(&cbb, &der, &der_len)) {
+      !CBB_flush(out)) {
     goto err;
   }
 
-  /* Convert back to legacy ASN.1 objects. */
-  const uint8_t *ptr = der;
-  ret = d2i_X509_SIG(NULL, &ptr, der_len);
-  if (ret == NULL || ptr != der + der_len) {
-    OPENSSL_PUT_ERROR(PKCS8, ERR_R_INTERNAL_ERROR);
-    X509_SIG_free(ret);
-    ret = NULL;
-  }
+  ret = 1;
 
 err:
-  if (plaintext_len > 0) {
+  if (plaintext != NULL) {
     OPENSSL_cleanse(plaintext, plaintext_len);
+    OPENSSL_free(plaintext);
   }
-  OPENSSL_free(plaintext);
   OPENSSL_free(salt_buf);
-  OPENSSL_free(der);
-  CBB_cleanup(&cbb);
   EVP_CIPHER_CTX_cleanup(&ctx);
   return ret;
 }
-
-X509_SIG *PKCS8_encrypt(int pbe_nid, const EVP_CIPHER *cipher, const char *pass,
-                        int pass_len, const uint8_t *salt, size_t salt_len,
-                        int iterations, PKCS8_PRIV_KEY_INFO *p8inf) {
-  uint8_t *pass_raw = NULL;
-  size_t pass_raw_len = 0;
-  if (!pass_to_pass_raw(pbe_nid, pass, pass_len, &pass_raw, &pass_raw_len)) {
-    return NULL;
-  }
-
-  X509_SIG *ret = pkcs8_encrypt_raw(pbe_nid, cipher, pass_raw, pass_raw_len,
-                                    salt, salt_len, iterations, p8inf);
-
-  if (pass_raw) {
-    OPENSSL_cleanse(pass_raw, pass_raw_len);
-    OPENSSL_free(pass_raw);
-  }
-  return ret;
-}
-
-EVP_PKEY *EVP_PKCS82PKEY(PKCS8_PRIV_KEY_INFO *p8) {
-  uint8_t *der = NULL;
-  int der_len = i2d_PKCS8_PRIV_KEY_INFO(p8, &der);
-  if (der_len < 0) {
-    return NULL;
-  }
-
-  CBS cbs;
-  CBS_init(&cbs, der, (size_t)der_len);
-  EVP_PKEY *ret = EVP_parse_private_key(&cbs);
-  if (ret == NULL || CBS_len(&cbs) != 0) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
-    EVP_PKEY_free(ret);
-    OPENSSL_free(der);
-    return NULL;
-  }
-
-  OPENSSL_free(der);
-  return ret;
-}
-
-PKCS8_PRIV_KEY_INFO *EVP_PKEY2PKCS8(EVP_PKEY *pkey) {
-  CBB cbb;
-  uint8_t *der = NULL;
-  size_t der_len;
-  if (!CBB_init(&cbb, 0) ||
-      !EVP_marshal_private_key(&cbb, pkey) ||
-      !CBB_finish(&cbb, &der, &der_len) ||
-      der_len > LONG_MAX) {
-    CBB_cleanup(&cbb);
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_ENCODE_ERROR);
-    goto err;
-  }
-
-  const uint8_t *p = der;
-  PKCS8_PRIV_KEY_INFO *p8 = d2i_PKCS8_PRIV_KEY_INFO(NULL, &p, (long)der_len);
-  if (p8 == NULL || p != der + der_len) {
-    PKCS8_PRIV_KEY_INFO_free(p8);
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
-    goto err;
-  }
-
-  OPENSSL_free(der);
-  return p8;
-
-err:
-  OPENSSL_free(der);
-  return NULL;
-}
-
-struct pkcs12_context {
-  EVP_PKEY **out_key;
-  STACK_OF(X509) *out_certs;
-  uint8_t *password;
-  size_t password_len;
-};
-
-/* PKCS12_handle_sequence parses a BER-encoded SEQUENCE of elements in a PKCS#12
- * structure. */
-static int PKCS12_handle_sequence(
-    CBS *sequence, struct pkcs12_context *ctx,
-    int (*handle_element)(CBS *cbs, struct pkcs12_context *ctx)) {
-  uint8_t *der_bytes = NULL;
-  size_t der_len;
-  CBS in;
-  int ret = 0;
-
-  /* Although a BER->DER conversion is done at the beginning of |PKCS12_parse|,
-   * the ASN.1 data gets wrapped in OCTETSTRINGs and/or encrypted and the
-   * conversion cannot see through those wrappings. So each time we step
-   * through one we need to convert to DER again. */
-  if (!CBS_asn1_ber_to_der(sequence, &der_bytes, &der_len)) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-    return 0;
-  }
-
-  if (der_bytes != NULL) {
-    CBS_init(&in, der_bytes, der_len);
-  } else {
-    CBS_init(&in, CBS_data(sequence), CBS_len(sequence));
-  }
-
-  CBS child;
-  if (!CBS_get_asn1(&in, &child, CBS_ASN1_SEQUENCE) ||
-      CBS_len(&in) != 0) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-    goto err;
-  }
-
-  while (CBS_len(&child) > 0) {
-    CBS element;
-    if (!CBS_get_asn1(&child, &element, CBS_ASN1_SEQUENCE)) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      goto err;
-    }
-
-    if (!handle_element(&element, ctx)) {
-      goto err;
-    }
-  }
-
-  ret = 1;
-
-err:
-  OPENSSL_free(der_bytes);
-  return ret;
-}
-
-/* PKCS12_handle_safe_bag parses a single SafeBag element in a PKCS#12
- * structure. */
-static int PKCS12_handle_safe_bag(CBS *safe_bag, struct pkcs12_context *ctx) {
-  CBS bag_id, wrapped_value;
-  if (!CBS_get_asn1(safe_bag, &bag_id, CBS_ASN1_OBJECT) ||
-      !CBS_get_asn1(safe_bag, &wrapped_value,
-                        CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)
-      /* Ignore the bagAttributes field. */) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-    return 0;
-  }
-
-  int nid = OBJ_cbs2nid(&bag_id);
-  if (nid == NID_pkcs8ShroudedKeyBag) {
-    /* See RFC 7292, section 4.2.2. */
-    if (*ctx->out_key) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_MULTIPLE_PRIVATE_KEYS_IN_PKCS12);
-      return 0;
-    }
-
-    if (CBS_len(&wrapped_value) > LONG_MAX) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      return 0;
-    }
-
-    /* |encrypted| isn't actually an X.509 signature, but it has the same
-     * structure as one and so |X509_SIG| is reused to store it. */
-    const uint8_t *inp = CBS_data(&wrapped_value);
-    X509_SIG *encrypted =
-        d2i_X509_SIG(NULL, &inp, (long)CBS_len(&wrapped_value));
-    if (encrypted == NULL) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      return 0;
-    }
-    if (inp != CBS_data(&wrapped_value) + CBS_len(&wrapped_value)) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      X509_SIG_free(encrypted);
-      return 0;
-    }
-
-    PKCS8_PRIV_KEY_INFO *pki =
-        pkcs8_decrypt_raw(encrypted, ctx->password, ctx->password_len);
-    X509_SIG_free(encrypted);
-    if (pki == NULL) {
-      return 0;
-    }
-
-    *ctx->out_key = EVP_PKCS82PKEY(pki);
-    PKCS8_PRIV_KEY_INFO_free(pki);
-    return ctx->out_key != NULL;
-  }
-
-  if (nid == NID_certBag) {
-    /* See RFC 7292, section 4.2.3. */
-    CBS cert_bag, cert_type, wrapped_cert, cert;
-    if (!CBS_get_asn1(&wrapped_value, &cert_bag, CBS_ASN1_SEQUENCE) ||
-        !CBS_get_asn1(&cert_bag, &cert_type, CBS_ASN1_OBJECT) ||
-        !CBS_get_asn1(&cert_bag, &wrapped_cert,
-                      CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
-        !CBS_get_asn1(&wrapped_cert, &cert, CBS_ASN1_OCTETSTRING)) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      return 0;
-    }
-
-    if (OBJ_cbs2nid(&cert_type) != NID_x509Certificate) {
-      return 1;
-    }
-
-    if (CBS_len(&cert) > LONG_MAX) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      return 0;
-    }
-
-    const uint8_t *inp = CBS_data(&cert);
-    X509 *x509 = d2i_X509(NULL, &inp, (long)CBS_len(&cert));
-    if (!x509) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      return 0;
-    }
-
-    if (inp != CBS_data(&cert) + CBS_len(&cert)) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      X509_free(x509);
-      return 0;
-    }
-
-    if (0 == sk_X509_push(ctx->out_certs, x509)) {
-      X509_free(x509);
-      return 0;
-    }
-
-    return 1;
-  }
-
-  /* Unknown element type - ignore it. */
-  return 1;
-}
-
-/* PKCS12_handle_content_info parses a single PKCS#7 ContentInfo element in a
- * PKCS#12 structure. */
-static int PKCS12_handle_content_info(CBS *content_info,
-                                      struct pkcs12_context *ctx) {
-  CBS content_type, wrapped_contents, contents;
-  int nid, ret = 0;
-  uint8_t *storage = NULL;
-
-  if (!CBS_get_asn1(content_info, &content_type, CBS_ASN1_OBJECT) ||
-      !CBS_get_asn1(content_info, &wrapped_contents,
-                        CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
-      CBS_len(content_info) != 0) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-    goto err;
-  }
-
-  nid = OBJ_cbs2nid(&content_type);
-  if (nid == NID_pkcs7_encrypted) {
-    /* See https://tools.ietf.org/html/rfc2315#section-13.
-     *
-     * PKCS#7 encrypted data inside a PKCS#12 structure is generally an
-     * encrypted certificate bag and it's generally encrypted with 40-bit
-     * RC2-CBC. */
-    CBS version_bytes, eci, contents_type, ai, encrypted_contents;
-    uint8_t *out;
-    size_t out_len;
-
-    if (!CBS_get_asn1(&wrapped_contents, &contents, CBS_ASN1_SEQUENCE) ||
-        !CBS_get_asn1(&contents, &version_bytes, CBS_ASN1_INTEGER) ||
-        /* EncryptedContentInfo, see
-         * https://tools.ietf.org/html/rfc2315#section-10.1 */
-        !CBS_get_asn1(&contents, &eci, CBS_ASN1_SEQUENCE) ||
-        !CBS_get_asn1(&eci, &contents_type, CBS_ASN1_OBJECT) ||
-        /* AlgorithmIdentifier, see
-         * https://tools.ietf.org/html/rfc5280#section-4.1.1.2 */
-        !CBS_get_asn1(&eci, &ai, CBS_ASN1_SEQUENCE) ||
-        !CBS_get_asn1_implicit_string(
-            &eci, &encrypted_contents, &storage,
-            CBS_ASN1_CONTEXT_SPECIFIC | 0, CBS_ASN1_OCTETSTRING)) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      goto err;
-    }
-
-    if (OBJ_cbs2nid(&contents_type) != NID_pkcs7_data) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      goto err;
-    }
-
-    if (!pbe_decrypt(&out, &out_len, &ai, ctx->password, ctx->password_len,
-                     CBS_data(&encrypted_contents),
-                     CBS_len(&encrypted_contents))) {
-      goto err;
-    }
-
-    CBS safe_contents;
-    CBS_init(&safe_contents, out, out_len);
-    ret = PKCS12_handle_sequence(&safe_contents, ctx, PKCS12_handle_safe_bag);
-    OPENSSL_free(out);
-  } else if (nid == NID_pkcs7_data) {
-    CBS octet_string_contents;
-
-    if (!CBS_get_asn1(&wrapped_contents, &octet_string_contents,
-                      CBS_ASN1_OCTETSTRING)) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      goto err;
-    }
-
-    ret = PKCS12_handle_sequence(&octet_string_contents, ctx,
-                                 PKCS12_handle_safe_bag);
-  } else {
-    /* Unknown element type - ignore it. */
-    ret = 1;
-  }
-
-err:
-  OPENSSL_free(storage);
-  return ret;
-}
-
-int PKCS12_get_key_and_certs(EVP_PKEY **out_key, STACK_OF(X509) *out_certs,
-                             CBS *ber_in, const char *password) {
-  uint8_t *der_bytes = NULL;
-  size_t der_len;
-  CBS in, pfx, mac_data, authsafe, content_type, wrapped_authsafes, authsafes;
-  uint64_t version;
-  int ret = 0;
-  struct pkcs12_context ctx;
-  const size_t original_out_certs_len = sk_X509_num(out_certs);
-
-  /* The input may be in BER format. */
-  if (!CBS_asn1_ber_to_der(ber_in, &der_bytes, &der_len)) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-    return 0;
-  }
-  if (der_bytes != NULL) {
-    CBS_init(&in, der_bytes, der_len);
-  } else {
-    CBS_init(&in, CBS_data(ber_in), CBS_len(ber_in));
-  }
-
-  *out_key = NULL;
-  OPENSSL_memset(&ctx, 0, sizeof(ctx));
-
-  /* See ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-12/pkcs-12v1.pdf, section
-   * four. */
-  if (!CBS_get_asn1(&in, &pfx, CBS_ASN1_SEQUENCE) ||
-      CBS_len(&in) != 0 ||
-      !CBS_get_asn1_uint64(&pfx, &version)) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-    goto err;
-  }
-
-  if (version < 3) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_VERSION);
-    goto err;
-  }
-
-  if (!CBS_get_asn1(&pfx, &authsafe, CBS_ASN1_SEQUENCE)) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-    goto err;
-  }
-
-  if (CBS_len(&pfx) == 0) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_MISSING_MAC);
-    goto err;
-  }
-
-  if (!CBS_get_asn1(&pfx, &mac_data, CBS_ASN1_SEQUENCE)) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-    goto err;
-  }
-
-  /* authsafe is a PKCS#7 ContentInfo. See
-   * https://tools.ietf.org/html/rfc2315#section-7. */
-  if (!CBS_get_asn1(&authsafe, &content_type, CBS_ASN1_OBJECT) ||
-      !CBS_get_asn1(&authsafe, &wrapped_authsafes,
-                        CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-    goto err;
-  }
-
-  /* The content type can either be |NID_pkcs7_data| or |NID_pkcs7_signed|. The
-   * latter indicates that it's signed by a public key, which isn't
-   * supported. */
-  if (OBJ_cbs2nid(&content_type) != NID_pkcs7_data) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_PKCS12_PUBLIC_KEY_INTEGRITY_NOT_SUPPORTED);
-    goto err;
-  }
-
-  if (!CBS_get_asn1(&wrapped_authsafes, &authsafes, CBS_ASN1_OCTETSTRING)) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-    goto err;
-  }
-
-  ctx.out_key = out_key;
-  ctx.out_certs = out_certs;
-  if (!ascii_to_ucs2(password, password ? strlen(password) : 0, &ctx.password,
-                     &ctx.password_len)) {
-    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
-    goto err;
-  }
-
-  /* Verify the MAC. */
-  {
-    CBS mac, hash_type_seq, hash_oid, salt, expected_mac;
-    uint64_t iterations;
-    int hash_nid;
-    const EVP_MD *md;
-    uint8_t hmac_key[EVP_MAX_MD_SIZE];
-    uint8_t hmac[EVP_MAX_MD_SIZE];
-    unsigned hmac_len;
-
-    if (!CBS_get_asn1(&mac_data, &mac, CBS_ASN1_SEQUENCE) ||
-        !CBS_get_asn1(&mac, &hash_type_seq, CBS_ASN1_SEQUENCE) ||
-        !CBS_get_asn1(&hash_type_seq, &hash_oid, CBS_ASN1_OBJECT) ||
-        !CBS_get_asn1(&mac, &expected_mac, CBS_ASN1_OCTETSTRING) ||
-        !CBS_get_asn1(&mac_data, &salt, CBS_ASN1_OCTETSTRING)) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-      goto err;
-    }
-
-    /* The iteration count is optional and the default is one. */
-    iterations = 1;
-    if (CBS_len(&mac_data) > 0) {
-      if (!CBS_get_asn1_uint64(&mac_data, &iterations) ||
-          iterations > UINT_MAX) {
-        OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
-        goto err;
-      }
-    }
-
-    hash_nid = OBJ_cbs2nid(&hash_oid);
-    if (hash_nid == NID_undef ||
-        (md = EVP_get_digestbynid(hash_nid)) == NULL) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_UNKNOWN_HASH);
-      goto err;
-    }
-
-    if (!pkcs12_key_gen_raw(ctx.password, ctx.password_len, CBS_data(&salt),
-                            CBS_len(&salt), PKCS12_MAC_ID, iterations,
-                            EVP_MD_size(md), hmac_key, md)) {
-      goto err;
-    }
-
-    if (NULL == HMAC(md, hmac_key, EVP_MD_size(md), CBS_data(&authsafes),
-                     CBS_len(&authsafes), hmac, &hmac_len)) {
-      goto err;
-    }
-
-    if (!CBS_mem_equal(&expected_mac, hmac, hmac_len)) {
-      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_INCORRECT_PASSWORD);
-      goto err;
-    }
-  }
-
-  /* authsafes contains a series of PKCS#7 ContentInfos. */
-  if (!PKCS12_handle_sequence(&authsafes, &ctx, PKCS12_handle_content_info)) {
-    goto err;
-  }
-
-  ret = 1;
-
-err:
-  OPENSSL_free(ctx.password);
-  OPENSSL_free(der_bytes);
-  if (!ret) {
-    EVP_PKEY_free(*out_key);
-    *out_key = NULL;
-    while (sk_X509_num(out_certs) > original_out_certs_len) {
-      X509 *x509 = sk_X509_pop(out_certs);
-      X509_free(x509);
-    }
-  }
-
-  return ret;
-}
-
-void PKCS12_PBE_add(void) {}
-
-struct pkcs12_st {
-  uint8_t *ber_bytes;
-  size_t ber_len;
-};
-
-PKCS12 *d2i_PKCS12(PKCS12 **out_p12, const uint8_t **ber_bytes,
-                   size_t ber_len) {
-  PKCS12 *p12;
-
-  p12 = OPENSSL_malloc(sizeof(PKCS12));
-  if (!p12) {
-    return NULL;
-  }
-
-  p12->ber_bytes = OPENSSL_malloc(ber_len);
-  if (!p12->ber_bytes) {
-    OPENSSL_free(p12);
-    return NULL;
-  }
-
-  OPENSSL_memcpy(p12->ber_bytes, *ber_bytes, ber_len);
-  p12->ber_len = ber_len;
-  *ber_bytes += ber_len;
-
-  if (out_p12) {
-    PKCS12_free(*out_p12);
-
-    *out_p12 = p12;
-  }
-
-  return p12;
-}
-
-PKCS12* d2i_PKCS12_bio(BIO *bio, PKCS12 **out_p12) {
-  size_t used = 0;
-  BUF_MEM *buf;
-  const uint8_t *dummy;
-  static const size_t kMaxSize = 256 * 1024;
-  PKCS12 *ret = NULL;
-
-  buf = BUF_MEM_new();
-  if (buf == NULL) {
-    return NULL;
-  }
-  if (BUF_MEM_grow(buf, 8192) == 0) {
-    goto out;
-  }
-
-  for (;;) {
-    int n = BIO_read(bio, &buf->data[used], buf->length - used);
-    if (n < 0) {
-      if (used == 0) {
-        goto out;
-      }
-      /* Workaround a bug in node.js. It uses a memory BIO for this in the wrong
-       * mode. */
-      n = 0;
-    }
-
-    if (n == 0) {
-      break;
-    }
-    used += n;
-
-    if (used < buf->length) {
-      continue;
-    }
-
-    if (buf->length > kMaxSize ||
-        BUF_MEM_grow(buf, buf->length * 2) == 0) {
-      goto out;
-    }
-  }
-
-  dummy = (uint8_t*) buf->data;
-  ret = d2i_PKCS12(out_p12, &dummy, used);
-
-out:
-  BUF_MEM_free(buf);
-  return ret;
-}
-
-PKCS12* d2i_PKCS12_fp(FILE *fp, PKCS12 **out_p12) {
-  BIO *bio;
-  PKCS12 *ret;
-
-  bio = BIO_new_fp(fp, 0 /* don't take ownership */);
-  if (!bio) {
-    return NULL;
-  }
-
-  ret = d2i_PKCS12_bio(bio, out_p12);
-  BIO_free(bio);
-  return ret;
-}
-
-int PKCS12_parse(const PKCS12 *p12, const char *password, EVP_PKEY **out_pkey,
-                 X509 **out_cert, STACK_OF(X509) **out_ca_certs) {
-  CBS ber_bytes;
-  STACK_OF(X509) *ca_certs = NULL;
-  char ca_certs_alloced = 0;
-
-  if (out_ca_certs != NULL && *out_ca_certs != NULL) {
-    ca_certs = *out_ca_certs;
-  }
-
-  if (!ca_certs) {
-    ca_certs = sk_X509_new_null();
-    if (ca_certs == NULL) {
-      OPENSSL_PUT_ERROR(PKCS8, ERR_R_MALLOC_FAILURE);
-      return 0;
-    }
-    ca_certs_alloced = 1;
-  }
-
-  CBS_init(&ber_bytes, p12->ber_bytes, p12->ber_len);
-  if (!PKCS12_get_key_and_certs(out_pkey, ca_certs, &ber_bytes, password)) {
-    if (ca_certs_alloced) {
-      sk_X509_free(ca_certs);
-    }
-    return 0;
-  }
-
-  *out_cert = NULL;
-  if (sk_X509_num(ca_certs) > 0) {
-    *out_cert = sk_X509_shift(ca_certs);
-  }
-
-  if (out_ca_certs) {
-    *out_ca_certs = ca_certs;
-  } else {
-    sk_X509_pop_free(ca_certs, X509_free);
-  }
-
-  return 1;
-}
-
-int PKCS12_verify_mac(const PKCS12 *p12, const char *password,
-                      int password_len) {
-  if (password == NULL) {
-    if (password_len != 0) {
-      return 0;
-    }
-  } else if (password_len != -1 &&
-             (password[password_len] != 0 ||
-              OPENSSL_memchr(password, 0, password_len) != NULL)) {
-    return 0;
-  }
-
-  EVP_PKEY *pkey = NULL;
-  X509 *cert = NULL;
-  if (!PKCS12_parse(p12, password, &pkey, &cert, NULL)) {
-    ERR_clear_error();
-    return 0;
-  }
-
-  EVP_PKEY_free(pkey);
-  X509_free(cert);
-
-  return 1;
-}
-
-void PKCS12_free(PKCS12 *p12) {
-  if (p12 == NULL) {
-    return;
-  }
-  OPENSSL_free(p12->ber_bytes);
-  OPENSSL_free(p12);
-}
diff --git a/src/crypto/pkcs8/pkcs8_test.cc b/src/crypto/pkcs8/pkcs8_test.cc
index 1196f9f..94385ac 100644
--- a/src/crypto/pkcs8/pkcs8_test.cc
+++ b/src/crypto/pkcs8/pkcs8_test.cc
@@ -143,6 +143,31 @@
     0xed,
 };
 
+// kExplicitHMACWithSHA1 is a PBES2-encrypted private key with an explicit
+// hmacWithSHA1 AlgorithmIdentifier in the PBKDF2 parameters.
+static const uint8_t kExplicitHMACWithSHA1[] = {
+    0x30, 0x81, 0xec, 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, 0x90,
+    0xcd, 0x1e, 0x47, 0x1d, 0xff, 0x4c, 0xa8, 0x02, 0x02, 0x08, 0x00, 0x30,
+    0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x07, 0x05,
+    0x00, 0x30, 0x1d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
+    0x01, 0x02, 0x04, 0x10, 0x34, 0xe7, 0x5b, 0x9b, 0xf9, 0x17, 0xcf, 0x15,
+    0x59, 0x7c, 0xfd, 0xc1, 0xac, 0xed, 0x6f, 0xdd, 0x04, 0x81, 0x90, 0xe3,
+    0xd7, 0xfc, 0xbe, 0xe6, 0xe8, 0x92, 0xc1, 0xa2, 0x57, 0x42, 0x4b, 0xf1,
+    0x35, 0x6c, 0x4f, 0x58, 0x61, 0x14, 0x30, 0x4e, 0xa3, 0x8d, 0x4f, 0xde,
+    0x2d, 0x0b, 0xa2, 0x62, 0x4b, 0xee, 0x9f, 0xc4, 0xeb, 0x89, 0x33, 0x76,
+    0x3f, 0x0c, 0x20, 0xad, 0x75, 0x29, 0x42, 0xbc, 0xbd, 0x83, 0x46, 0x1d,
+    0x5c, 0xae, 0xec, 0x10, 0x05, 0xbb, 0xd3, 0x98, 0xc9, 0x5a, 0x5e, 0x0a,
+    0x95, 0x12, 0x1e, 0x65, 0x93, 0xdd, 0xdd, 0x51, 0xd5, 0x56, 0xc2, 0xa9,
+    0xf9, 0x43, 0x0f, 0x68, 0x8a, 0x14, 0x40, 0xe5, 0x62, 0x9e, 0x0d, 0xd7,
+    0x67, 0x62, 0xf4, 0x49, 0xb1, 0x62, 0x22, 0x42, 0xb1, 0xe1, 0xb2, 0x1d,
+    0x37, 0x3e, 0x95, 0x52, 0xe9, 0x61, 0x89, 0xc7, 0x62, 0xcc, 0xb1, 0x44,
+    0x40, 0xef, 0x89, 0xc8, 0xc4, 0x0e, 0xae, 0xa8, 0xf9, 0x17, 0x42, 0x2b,
+    0x8c, 0x0b, 0x26, 0xf6, 0x07, 0x00, 0xab, 0x25, 0x2b, 0x64, 0xcf, 0xc3,
+    0x68, 0xf9, 0x5e, 0x01, 0x66, 0x59, 0x5f, 0x3f, 0x05, 0x57, 0xcd,
+};
+
 static bool TestDecrypt(const uint8_t *der, size_t der_len,
                         const char *password) {
   const uint8_t *data = der;
@@ -222,6 +247,8 @@
       !TestDecrypt(kNullPassword, sizeof(kNullPassword), NULL) ||
       !TestDecrypt(kNullPasswordNSS, sizeof(kNullPasswordNSS), NULL) ||
       !TestDecrypt(kEmptyPasswordOpenSSL, sizeof(kEmptyPasswordOpenSSL), "") ||
+      !TestDecrypt(kExplicitHMACWithSHA1, sizeof(kExplicitHMACWithSHA1),
+                   "foo") ||
       !TestRoundTrip(NID_pbe_WithSHA1And3_Key_TripleDES_CBC, nullptr,
                      "password", nullptr, 0, 10) ||
       // Vary the salt
diff --git a/src/crypto/pkcs8/pkcs8_x509.c b/src/crypto/pkcs8/pkcs8_x509.c
new file mode 100644
index 0000000..242f911
--- /dev/null
+++ b/src/crypto/pkcs8/pkcs8_x509.c
@@ -0,0 +1,790 @@
+/* Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL
+ * project 1999.
+ */
+/* ====================================================================
+ * Copyright (c) 1999 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
+ *    licensing@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.
+ * ====================================================================
+ *
+ * This product includes cryptographic software written by Eric Young
+ * (eay@cryptsoft.com).  This product includes software written by Tim
+ * Hudson (tjh@cryptsoft.com). */
+
+#include <openssl/pkcs8.h>
+
+#include <limits.h>
+
+#include <openssl/asn1t.h>
+#include <openssl/asn1.h>
+#include <openssl/bio.h>
+#include <openssl/buf.h>
+#include <openssl/bytestring.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/digest.h>
+#include <openssl/hmac.h>
+#include <openssl/mem.h>
+#include <openssl/x509.h>
+
+#include "internal.h"
+#include "../bytestring/internal.h"
+#include "../digest/internal.h"
+#include "../internal.h"
+
+
+/* Minor tweak to operation: zero private key data */
+static int pkey_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it,
+                   void *exarg) {
+  /* Since the structure must still be valid use ASN1_OP_FREE_PRE */
+  if (operation == ASN1_OP_FREE_PRE) {
+    PKCS8_PRIV_KEY_INFO *key = (PKCS8_PRIV_KEY_INFO *)*pval;
+    if (key->pkey && key->pkey->type == V_ASN1_OCTET_STRING &&
+        key->pkey->value.octet_string) {
+      OPENSSL_cleanse(key->pkey->value.octet_string->data,
+                      key->pkey->value.octet_string->length);
+    }
+  }
+  return 1;
+}
+
+ASN1_SEQUENCE_cb(PKCS8_PRIV_KEY_INFO, pkey_cb) = {
+  ASN1_SIMPLE(PKCS8_PRIV_KEY_INFO, version, ASN1_INTEGER),
+  ASN1_SIMPLE(PKCS8_PRIV_KEY_INFO, pkeyalg, X509_ALGOR),
+  ASN1_SIMPLE(PKCS8_PRIV_KEY_INFO, pkey, ASN1_ANY),
+  ASN1_IMP_SET_OF_OPT(PKCS8_PRIV_KEY_INFO, attributes, X509_ATTRIBUTE, 0)
+} ASN1_SEQUENCE_END_cb(PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO)
+
+IMPLEMENT_ASN1_FUNCTIONS(PKCS8_PRIV_KEY_INFO)
+
+EVP_PKEY *EVP_PKCS82PKEY(PKCS8_PRIV_KEY_INFO *p8) {
+  uint8_t *der = NULL;
+  int der_len = i2d_PKCS8_PRIV_KEY_INFO(p8, &der);
+  if (der_len < 0) {
+    return NULL;
+  }
+
+  CBS cbs;
+  CBS_init(&cbs, der, (size_t)der_len);
+  EVP_PKEY *ret = EVP_parse_private_key(&cbs);
+  if (ret == NULL || CBS_len(&cbs) != 0) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
+    EVP_PKEY_free(ret);
+    OPENSSL_free(der);
+    return NULL;
+  }
+
+  OPENSSL_free(der);
+  return ret;
+}
+
+PKCS8_PRIV_KEY_INFO *EVP_PKEY2PKCS8(EVP_PKEY *pkey) {
+  CBB cbb;
+  uint8_t *der = NULL;
+  size_t der_len;
+  if (!CBB_init(&cbb, 0) ||
+      !EVP_marshal_private_key(&cbb, pkey) ||
+      !CBB_finish(&cbb, &der, &der_len) ||
+      der_len > LONG_MAX) {
+    CBB_cleanup(&cbb);
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_ENCODE_ERROR);
+    goto err;
+  }
+
+  const uint8_t *p = der;
+  PKCS8_PRIV_KEY_INFO *p8 = d2i_PKCS8_PRIV_KEY_INFO(NULL, &p, (long)der_len);
+  if (p8 == NULL || p != der + der_len) {
+    PKCS8_PRIV_KEY_INFO_free(p8);
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_DECODE_ERROR);
+    goto err;
+  }
+
+  OPENSSL_free(der);
+  return p8;
+
+err:
+  OPENSSL_free(der);
+  return NULL;
+}
+
+PKCS8_PRIV_KEY_INFO *PKCS8_decrypt(X509_SIG *pkcs8, const char *pass,
+                                   int pass_len_in) {
+  size_t pass_len;
+  if (pass_len_in == -1 && pass != NULL) {
+    pass_len = strlen(pass);
+  } else {
+    pass_len = (size_t)pass_len_in;
+  }
+
+  PKCS8_PRIV_KEY_INFO *ret = NULL;
+  EVP_PKEY *pkey = NULL;
+  uint8_t *in = NULL;
+
+  /* Convert the legacy ASN.1 object to a byte string. */
+  int in_len = i2d_X509_SIG(pkcs8, &in);
+  if (in_len < 0) {
+    goto err;
+  }
+
+  CBS cbs;
+  CBS_init(&cbs, in, in_len);
+  pkey = PKCS8_parse_encrypted_private_key(&cbs, pass, pass_len);
+  if (pkey == NULL || CBS_len(&cbs) != 0) {
+    goto err;
+  }
+
+  ret = EVP_PKEY2PKCS8(pkey);
+
+err:
+  OPENSSL_free(in);
+  EVP_PKEY_free(pkey);
+  return ret;
+}
+
+X509_SIG *PKCS8_encrypt(int pbe_nid, const EVP_CIPHER *cipher, const char *pass,
+                        int pass_len_in, const uint8_t *salt, size_t salt_len,
+                        int iterations, PKCS8_PRIV_KEY_INFO *p8inf) {
+  size_t pass_len;
+  if (pass_len_in == -1 && pass != NULL) {
+    pass_len = strlen(pass);
+  } else {
+    pass_len = (size_t)pass_len_in;
+  }
+
+  /* Parse out the private key. */
+  EVP_PKEY *pkey = EVP_PKCS82PKEY(p8inf);
+  if (pkey == NULL) {
+    return NULL;
+  }
+
+  X509_SIG *ret = NULL;
+  uint8_t *der = NULL;
+  size_t der_len;
+  CBB cbb;
+  if (!CBB_init(&cbb, 128) ||
+      !PKCS8_marshal_encrypted_private_key(&cbb, pbe_nid, cipher, pass,
+                                           pass_len, salt, salt_len, iterations,
+                                           pkey) ||
+      !CBB_finish(&cbb, &der, &der_len)) {
+    CBB_cleanup(&cbb);
+    goto err;
+  }
+
+  /* Convert back to legacy ASN.1 objects. */
+  const uint8_t *ptr = der;
+  ret = d2i_X509_SIG(NULL, &ptr, der_len);
+  if (ret == NULL || ptr != der + der_len) {
+    OPENSSL_PUT_ERROR(PKCS8, ERR_R_INTERNAL_ERROR);
+    X509_SIG_free(ret);
+    ret = NULL;
+  }
+
+err:
+  OPENSSL_free(der);
+  EVP_PKEY_free(pkey);
+  return ret;
+}
+
+struct pkcs12_context {
+  EVP_PKEY **out_key;
+  STACK_OF(X509) *out_certs;
+  const char *password;
+  size_t password_len;
+};
+
+/* PKCS12_handle_sequence parses a BER-encoded SEQUENCE of elements in a PKCS#12
+ * structure. */
+static int PKCS12_handle_sequence(
+    CBS *sequence, struct pkcs12_context *ctx,
+    int (*handle_element)(CBS *cbs, struct pkcs12_context *ctx)) {
+  uint8_t *der_bytes = NULL;
+  size_t der_len;
+  CBS in;
+  int ret = 0;
+
+  /* Although a BER->DER conversion is done at the beginning of |PKCS12_parse|,
+   * the ASN.1 data gets wrapped in OCTETSTRINGs and/or encrypted and the
+   * conversion cannot see through those wrappings. So each time we step
+   * through one we need to convert to DER again. */
+  if (!CBS_asn1_ber_to_der(sequence, &der_bytes, &der_len)) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+    return 0;
+  }
+
+  if (der_bytes != NULL) {
+    CBS_init(&in, der_bytes, der_len);
+  } else {
+    CBS_init(&in, CBS_data(sequence), CBS_len(sequence));
+  }
+
+  CBS child;
+  if (!CBS_get_asn1(&in, &child, CBS_ASN1_SEQUENCE) ||
+      CBS_len(&in) != 0) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+    goto err;
+  }
+
+  while (CBS_len(&child) > 0) {
+    CBS element;
+    if (!CBS_get_asn1(&child, &element, CBS_ASN1_SEQUENCE)) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      goto err;
+    }
+
+    if (!handle_element(&element, ctx)) {
+      goto err;
+    }
+  }
+
+  ret = 1;
+
+err:
+  OPENSSL_free(der_bytes);
+  return ret;
+}
+
+/* 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};
+
+/* 1.2.840.113549.1.12.10.1.3 */
+static const uint8_t kCertBag[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+                                   0x01, 0x0c, 0x0a, 0x01, 0x03};
+
+/* 1.2.840.113549.1.9.22.1 */
+static const uint8_t kX509Certificate[] = {0x2a, 0x86, 0x48, 0x86, 0xf7,
+                                           0x0d, 0x01, 0x09, 0x16, 0x01};
+
+/* PKCS12_handle_safe_bag parses a single SafeBag element in a PKCS#12
+ * structure. */
+static int PKCS12_handle_safe_bag(CBS *safe_bag, struct pkcs12_context *ctx) {
+  CBS bag_id, wrapped_value;
+  if (!CBS_get_asn1(safe_bag, &bag_id, CBS_ASN1_OBJECT) ||
+      !CBS_get_asn1(safe_bag, &wrapped_value,
+                        CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)
+      /* Ignore the bagAttributes field. */) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+    return 0;
+  }
+
+  if (CBS_mem_equal(&bag_id, kPKCS8ShroudedKeyBag,
+                    sizeof(kPKCS8ShroudedKeyBag))) {
+    /* See RFC 7292, section 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);
+    if (pkey == NULL) {
+      return 0;
+    }
+
+    if (CBS_len(&wrapped_value) != 0) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      EVP_PKEY_free(pkey);
+      return 0;
+    }
+
+    *ctx->out_key = pkey;
+    return 1;
+  }
+
+  if (CBS_mem_equal(&bag_id, kCertBag, sizeof(kCertBag))) {
+    /* See RFC 7292, section 4.2.3. */
+    CBS cert_bag, cert_type, wrapped_cert, cert;
+    if (!CBS_get_asn1(&wrapped_value, &cert_bag, CBS_ASN1_SEQUENCE) ||
+        !CBS_get_asn1(&cert_bag, &cert_type, CBS_ASN1_OBJECT) ||
+        !CBS_get_asn1(&cert_bag, &wrapped_cert,
+                      CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
+        !CBS_get_asn1(&wrapped_cert, &cert, CBS_ASN1_OCTETSTRING)) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      return 0;
+    }
+
+    /* Skip unknown certificate types. */
+    if (!CBS_mem_equal(&cert_type, kX509Certificate,
+                       sizeof(kX509Certificate))) {
+      return 1;
+    }
+
+    if (CBS_len(&cert) > LONG_MAX) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      return 0;
+    }
+
+    const uint8_t *inp = CBS_data(&cert);
+    X509 *x509 = d2i_X509(NULL, &inp, (long)CBS_len(&cert));
+    if (!x509) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      return 0;
+    }
+
+    if (inp != CBS_data(&cert) + CBS_len(&cert)) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      X509_free(x509);
+      return 0;
+    }
+
+    if (0 == sk_X509_push(ctx->out_certs, x509)) {
+      X509_free(x509);
+      return 0;
+    }
+
+    return 1;
+  }
+
+  /* Unknown element type - ignore it. */
+  return 1;
+}
+
+/* 1.2.840.113549.1.7.1 */
+static const uint8_t kPKCS7Data[] = {0x2a, 0x86, 0x48, 0x86, 0xf7,
+                                     0x0d, 0x01, 0x07, 0x01};
+
+/* 1.2.840.113549.1.7.6 */
+static const uint8_t kPKCS7EncryptedData[] = {0x2a, 0x86, 0x48, 0x86, 0xf7,
+                                              0x0d, 0x01, 0x07, 0x06};
+
+/* PKCS12_handle_content_info parses a single PKCS#7 ContentInfo element in a
+ * PKCS#12 structure. */
+static int PKCS12_handle_content_info(CBS *content_info,
+                                      struct pkcs12_context *ctx) {
+  CBS content_type, wrapped_contents, contents;
+  int ret = 0;
+  uint8_t *storage = NULL;
+
+  if (!CBS_get_asn1(content_info, &content_type, CBS_ASN1_OBJECT) ||
+      !CBS_get_asn1(content_info, &wrapped_contents,
+                        CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
+      CBS_len(content_info) != 0) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+    goto err;
+  }
+
+  if (CBS_mem_equal(&content_type, kPKCS7EncryptedData,
+                    sizeof(kPKCS7EncryptedData))) {
+    /* See https://tools.ietf.org/html/rfc2315#section-13.
+     *
+     * PKCS#7 encrypted data inside a PKCS#12 structure is generally an
+     * encrypted certificate bag and it's generally encrypted with 40-bit
+     * RC2-CBC. */
+    CBS version_bytes, eci, contents_type, ai, encrypted_contents;
+    uint8_t *out;
+    size_t out_len;
+
+    if (!CBS_get_asn1(&wrapped_contents, &contents, CBS_ASN1_SEQUENCE) ||
+        !CBS_get_asn1(&contents, &version_bytes, CBS_ASN1_INTEGER) ||
+        /* EncryptedContentInfo, see
+         * https://tools.ietf.org/html/rfc2315#section-10.1 */
+        !CBS_get_asn1(&contents, &eci, CBS_ASN1_SEQUENCE) ||
+        !CBS_get_asn1(&eci, &contents_type, CBS_ASN1_OBJECT) ||
+        /* AlgorithmIdentifier, see
+         * https://tools.ietf.org/html/rfc5280#section-4.1.1.2 */
+        !CBS_get_asn1(&eci, &ai, CBS_ASN1_SEQUENCE) ||
+        !CBS_get_asn1_implicit_string(
+            &eci, &encrypted_contents, &storage,
+            CBS_ASN1_CONTEXT_SPECIFIC | 0, CBS_ASN1_OCTETSTRING)) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      goto err;
+    }
+
+    if (!CBS_mem_equal(&contents_type, kPKCS7Data, sizeof(kPKCS7Data))) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      goto err;
+    }
+
+    if (!pkcs8_pbe_decrypt(&out, &out_len, &ai, ctx->password,
+                           ctx->password_len, CBS_data(&encrypted_contents),
+                           CBS_len(&encrypted_contents))) {
+      goto err;
+    }
+
+    CBS safe_contents;
+    CBS_init(&safe_contents, out, out_len);
+    ret = PKCS12_handle_sequence(&safe_contents, ctx, PKCS12_handle_safe_bag);
+    OPENSSL_free(out);
+  } else if (CBS_mem_equal(&content_type, kPKCS7Data, sizeof(kPKCS7Data))) {
+    CBS octet_string_contents;
+
+    if (!CBS_get_asn1(&wrapped_contents, &octet_string_contents,
+                      CBS_ASN1_OCTETSTRING)) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      goto err;
+    }
+
+    ret = PKCS12_handle_sequence(&octet_string_contents, ctx,
+                                 PKCS12_handle_safe_bag);
+  } else {
+    /* Unknown element type - ignore it. */
+    ret = 1;
+  }
+
+err:
+  OPENSSL_free(storage);
+  return ret;
+}
+
+int PKCS12_get_key_and_certs(EVP_PKEY **out_key, STACK_OF(X509) *out_certs,
+                             CBS *ber_in, const char *password) {
+  uint8_t *der_bytes = NULL;
+  size_t der_len;
+  CBS in, pfx, mac_data, authsafe, content_type, wrapped_authsafes, authsafes;
+  uint64_t version;
+  int ret = 0;
+  struct pkcs12_context ctx;
+  const size_t original_out_certs_len = sk_X509_num(out_certs);
+
+  /* The input may be in BER format. */
+  if (!CBS_asn1_ber_to_der(ber_in, &der_bytes, &der_len)) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+    return 0;
+  }
+  if (der_bytes != NULL) {
+    CBS_init(&in, der_bytes, der_len);
+  } else {
+    CBS_init(&in, CBS_data(ber_in), CBS_len(ber_in));
+  }
+
+  *out_key = NULL;
+  OPENSSL_memset(&ctx, 0, sizeof(ctx));
+
+  /* See ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-12/pkcs-12v1.pdf, section
+   * four. */
+  if (!CBS_get_asn1(&in, &pfx, CBS_ASN1_SEQUENCE) ||
+      CBS_len(&in) != 0 ||
+      !CBS_get_asn1_uint64(&pfx, &version)) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+    goto err;
+  }
+
+  if (version < 3) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_VERSION);
+    goto err;
+  }
+
+  if (!CBS_get_asn1(&pfx, &authsafe, CBS_ASN1_SEQUENCE)) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+    goto err;
+  }
+
+  if (CBS_len(&pfx) == 0) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_MISSING_MAC);
+    goto err;
+  }
+
+  if (!CBS_get_asn1(&pfx, &mac_data, CBS_ASN1_SEQUENCE)) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+    goto err;
+  }
+
+  /* authsafe is a PKCS#7 ContentInfo. See
+   * https://tools.ietf.org/html/rfc2315#section-7. */
+  if (!CBS_get_asn1(&authsafe, &content_type, CBS_ASN1_OBJECT) ||
+      !CBS_get_asn1(&authsafe, &wrapped_authsafes,
+                        CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+    goto err;
+  }
+
+  /* The content type can either be data or signedData. The latter indicates
+   * that it's signed by a public key, which isn't supported. */
+  if (!CBS_mem_equal(&content_type, kPKCS7Data, sizeof(kPKCS7Data))) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_PKCS12_PUBLIC_KEY_INTEGRITY_NOT_SUPPORTED);
+    goto err;
+  }
+
+  if (!CBS_get_asn1(&wrapped_authsafes, &authsafes, CBS_ASN1_OCTETSTRING)) {
+    OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+    goto err;
+  }
+
+  ctx.out_key = out_key;
+  ctx.out_certs = out_certs;
+  ctx.password = password;
+  ctx.password_len = password != NULL ? strlen(password) : 0;
+
+  /* Verify the MAC. */
+  {
+    CBS mac, salt, expected_mac;
+    if (!CBS_get_asn1(&mac_data, &mac, CBS_ASN1_SEQUENCE)) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      goto err;
+    }
+
+    const EVP_MD *md = EVP_parse_digest_algorithm(&mac);
+    if (md == NULL) {
+      goto err;
+    }
+
+    if (!CBS_get_asn1(&mac, &expected_mac, CBS_ASN1_OCTETSTRING) ||
+        !CBS_get_asn1(&mac_data, &salt, CBS_ASN1_OCTETSTRING)) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+      goto err;
+    }
+
+    /* The iteration count is optional and the default is one. */
+    uint64_t iterations = 1;
+    if (CBS_len(&mac_data) > 0) {
+      if (!CBS_get_asn1_uint64(&mac_data, &iterations) ||
+          iterations > UINT_MAX) {
+        OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
+        goto err;
+      }
+    }
+
+    uint8_t hmac_key[EVP_MAX_MD_SIZE];
+    if (!pkcs12_key_gen(ctx.password, ctx.password_len, CBS_data(&salt),
+                        CBS_len(&salt), PKCS12_MAC_ID, iterations,
+                        EVP_MD_size(md), hmac_key, md)) {
+      goto err;
+    }
+
+    uint8_t hmac[EVP_MAX_MD_SIZE];
+    unsigned hmac_len;
+    if (NULL == HMAC(md, hmac_key, EVP_MD_size(md), CBS_data(&authsafes),
+                     CBS_len(&authsafes), hmac, &hmac_len)) {
+      goto err;
+    }
+
+    if (!CBS_mem_equal(&expected_mac, hmac, hmac_len)) {
+      OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_INCORRECT_PASSWORD);
+      goto err;
+    }
+  }
+
+  /* authsafes contains a series of PKCS#7 ContentInfos. */
+  if (!PKCS12_handle_sequence(&authsafes, &ctx, PKCS12_handle_content_info)) {
+    goto err;
+  }
+
+  ret = 1;
+
+err:
+  OPENSSL_free(der_bytes);
+  if (!ret) {
+    EVP_PKEY_free(*out_key);
+    *out_key = NULL;
+    while (sk_X509_num(out_certs) > original_out_certs_len) {
+      X509 *x509 = sk_X509_pop(out_certs);
+      X509_free(x509);
+    }
+  }
+
+  return ret;
+}
+
+void PKCS12_PBE_add(void) {}
+
+struct pkcs12_st {
+  uint8_t *ber_bytes;
+  size_t ber_len;
+};
+
+PKCS12 *d2i_PKCS12(PKCS12 **out_p12, const uint8_t **ber_bytes,
+                   size_t ber_len) {
+  PKCS12 *p12;
+
+  p12 = OPENSSL_malloc(sizeof(PKCS12));
+  if (!p12) {
+    return NULL;
+  }
+
+  p12->ber_bytes = OPENSSL_malloc(ber_len);
+  if (!p12->ber_bytes) {
+    OPENSSL_free(p12);
+    return NULL;
+  }
+
+  OPENSSL_memcpy(p12->ber_bytes, *ber_bytes, ber_len);
+  p12->ber_len = ber_len;
+  *ber_bytes += ber_len;
+
+  if (out_p12) {
+    PKCS12_free(*out_p12);
+
+    *out_p12 = p12;
+  }
+
+  return p12;
+}
+
+PKCS12* d2i_PKCS12_bio(BIO *bio, PKCS12 **out_p12) {
+  size_t used = 0;
+  BUF_MEM *buf;
+  const uint8_t *dummy;
+  static const size_t kMaxSize = 256 * 1024;
+  PKCS12 *ret = NULL;
+
+  buf = BUF_MEM_new();
+  if (buf == NULL) {
+    return NULL;
+  }
+  if (BUF_MEM_grow(buf, 8192) == 0) {
+    goto out;
+  }
+
+  for (;;) {
+    int n = BIO_read(bio, &buf->data[used], buf->length - used);
+    if (n < 0) {
+      if (used == 0) {
+        goto out;
+      }
+      /* Workaround a bug in node.js. It uses a memory BIO for this in the wrong
+       * mode. */
+      n = 0;
+    }
+
+    if (n == 0) {
+      break;
+    }
+    used += n;
+
+    if (used < buf->length) {
+      continue;
+    }
+
+    if (buf->length > kMaxSize ||
+        BUF_MEM_grow(buf, buf->length * 2) == 0) {
+      goto out;
+    }
+  }
+
+  dummy = (uint8_t*) buf->data;
+  ret = d2i_PKCS12(out_p12, &dummy, used);
+
+out:
+  BUF_MEM_free(buf);
+  return ret;
+}
+
+PKCS12* d2i_PKCS12_fp(FILE *fp, PKCS12 **out_p12) {
+  BIO *bio;
+  PKCS12 *ret;
+
+  bio = BIO_new_fp(fp, 0 /* don't take ownership */);
+  if (!bio) {
+    return NULL;
+  }
+
+  ret = d2i_PKCS12_bio(bio, out_p12);
+  BIO_free(bio);
+  return ret;
+}
+
+int PKCS12_parse(const PKCS12 *p12, const char *password, EVP_PKEY **out_pkey,
+                 X509 **out_cert, STACK_OF(X509) **out_ca_certs) {
+  CBS ber_bytes;
+  STACK_OF(X509) *ca_certs = NULL;
+  char ca_certs_alloced = 0;
+
+  if (out_ca_certs != NULL && *out_ca_certs != NULL) {
+    ca_certs = *out_ca_certs;
+  }
+
+  if (!ca_certs) {
+    ca_certs = sk_X509_new_null();
+    if (ca_certs == NULL) {
+      OPENSSL_PUT_ERROR(PKCS8, ERR_R_MALLOC_FAILURE);
+      return 0;
+    }
+    ca_certs_alloced = 1;
+  }
+
+  CBS_init(&ber_bytes, p12->ber_bytes, p12->ber_len);
+  if (!PKCS12_get_key_and_certs(out_pkey, ca_certs, &ber_bytes, password)) {
+    if (ca_certs_alloced) {
+      sk_X509_free(ca_certs);
+    }
+    return 0;
+  }
+
+  *out_cert = NULL;
+  if (sk_X509_num(ca_certs) > 0) {
+    *out_cert = sk_X509_shift(ca_certs);
+  }
+
+  if (out_ca_certs) {
+    *out_ca_certs = ca_certs;
+  } else {
+    sk_X509_pop_free(ca_certs, X509_free);
+  }
+
+  return 1;
+}
+
+int PKCS12_verify_mac(const PKCS12 *p12, const char *password,
+                      int password_len) {
+  if (password == NULL) {
+    if (password_len != 0) {
+      return 0;
+    }
+  } else if (password_len != -1 &&
+             (password[password_len] != 0 ||
+              OPENSSL_memchr(password, 0, password_len) != NULL)) {
+    return 0;
+  }
+
+  EVP_PKEY *pkey = NULL;
+  X509 *cert = NULL;
+  if (!PKCS12_parse(p12, password, &pkey, &cert, NULL)) {
+    ERR_clear_error();
+    return 0;
+  }
+
+  EVP_PKEY_free(pkey);
+  X509_free(cert);
+
+  return 1;
+}
+
+void PKCS12_free(PKCS12 *p12) {
+  if (p12 == NULL) {
+    return;
+  }
+  OPENSSL_free(p12->ber_bytes);
+  OPENSSL_free(p12);
+}
diff --git a/src/crypto/rand/deterministic.c b/src/crypto/rand/deterministic.c
index d96a505..8c754c1 100644
--- a/src/crypto/rand/deterministic.c
+++ b/src/crypto/rand/deterministic.c
@@ -24,11 +24,11 @@
 #include "../internal.h"
 
 
-/* g_num_calls is the number of calls to |CRYPTO_sysrand| that have occured.
+/* g_num_calls is the number of calls to |CRYPTO_sysrand| that have occurred.
  *
- * TODO(davidben): This is intentionally not thread-safe. If the fuzzer mode is
- * ever used in a multi-threaded program, replace this with a thread-local. (A
- * mutex would not be deterministic.) */
+ * This is intentionally not thread-safe. If the fuzzer mode is ever used in a
+ * multi-threaded program, replace this with a thread-local. (A mutex would not
+ * be deterministic.) */
 static uint64_t g_num_calls = 0;
 
 void RAND_reset_for_fuzzing(void) { g_num_calls = 0; }
diff --git a/src/crypto/rsa/internal.h b/src/crypto/rsa/internal.h
index c6ea97f..b6a0727 100644
--- a/src/crypto/rsa/internal.h
+++ b/src/crypto/rsa/internal.h
@@ -95,24 +95,20 @@
                        BN_CTX *ctx);
 
 
-int RSA_padding_add_PKCS1_type_1(uint8_t *to, unsigned to_len,
-                                 const uint8_t *from, unsigned from_len);
+int RSA_padding_add_PKCS1_type_1(uint8_t *to, size_t to_len,
+                                 const uint8_t *from, size_t from_len);
 int RSA_padding_check_PKCS1_type_1(uint8_t *to, unsigned to_len,
                                    const uint8_t *from, unsigned from_len);
-int RSA_padding_add_PKCS1_type_2(uint8_t *to, unsigned to_len,
-                                 const uint8_t *from, unsigned from_len);
+int RSA_padding_add_PKCS1_type_2(uint8_t *to, size_t to_len,
+                                 const uint8_t *from, size_t from_len);
 int RSA_padding_check_PKCS1_type_2(uint8_t *to, unsigned to_len,
                                    const uint8_t *from, unsigned from_len);
-int RSA_padding_add_PKCS1_OAEP_mgf1(uint8_t *to, unsigned to_len,
-                                    const uint8_t *from, unsigned from_len,
-                                    const uint8_t *param, unsigned plen,
-                                    const EVP_MD *md, const EVP_MD *mgf1md);
 int RSA_padding_check_PKCS1_OAEP_mgf1(uint8_t *to, unsigned to_len,
                                       const uint8_t *from, unsigned from_len,
                                       const uint8_t *param, unsigned plen,
                                       const EVP_MD *md, const EVP_MD *mgf1md);
-int RSA_padding_add_none(uint8_t *to, unsigned to_len, const uint8_t *from,
-                         unsigned from_len);
+int RSA_padding_add_none(uint8_t *to, size_t to_len, const uint8_t *from,
+                         size_t from_len);
 
 /* RSA_private_transform calls either the method-specific |private_transform|
  * function (if given) or the generic one. See the comment for
diff --git a/src/crypto/rsa/padding.c b/src/crypto/rsa/padding.c
index 678457b..ac583c4 100644
--- a/src/crypto/rsa/padding.c
+++ b/src/crypto/rsa/padding.c
@@ -71,10 +71,9 @@
 
 /* TODO(fork): don't the check functions have to be constant time? */
 
-int RSA_padding_add_PKCS1_type_1(uint8_t *to, unsigned to_len,
-                                 const uint8_t *from, unsigned from_len) {
-  unsigned j;
-
+int RSA_padding_add_PKCS1_type_1(uint8_t *to, size_t to_len,
+                                 const uint8_t *from, size_t from_len) {
+  /* See RFC 8017, section 9.2. */
   if (to_len < RSA_PKCS1_PADDING_SIZE) {
     OPENSSL_PUT_ERROR(RSA, RSA_R_KEY_SIZE_TOO_SMALL);
     return 0;
@@ -85,17 +84,11 @@
     return 0;
   }
 
-  uint8_t *p = to;
-
-  *(p++) = 0;
-  *(p++) = 1; /* Private Key BT (Block Type) */
-
-  /* pad out with 0xff data */
-  j = to_len - 3 - from_len;
-  OPENSSL_memset(p, 0xff, j);
-  p += j;
-  *(p++) = 0;
-  OPENSSL_memcpy(p, from, from_len);
+  to[0] = 0;
+  to[1] = 1;
+  OPENSSL_memset(to + 2, 0xff, to_len - 3 - from_len);
+  to[to_len - from_len - 1] = 0;
+  OPENSSL_memcpy(to + to_len - from_len, from, from_len);
   return 1;
 }
 
@@ -151,10 +144,25 @@
   return j;
 }
 
-int RSA_padding_add_PKCS1_type_2(uint8_t *to, unsigned to_len,
-                                 const uint8_t *from, unsigned from_len) {
-  unsigned i, j;
+static int rand_nonzero(uint8_t *out, size_t len) {
+  if (!RAND_bytes(out, len)) {
+    return 0;
+  }
 
+  for (size_t i = 0; i < len; i++) {
+    while (out[i] == 0) {
+      if (!RAND_bytes(out + i, 1)) {
+        return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
+int RSA_padding_add_PKCS1_type_2(uint8_t *to, size_t to_len,
+                                 const uint8_t *from, size_t from_len) {
+  /* See RFC 8017, section 7.2.1. */
   if (to_len < RSA_PKCS1_PADDING_SIZE) {
     OPENSSL_PUT_ERROR(RSA, RSA_R_KEY_SIZE_TOO_SMALL);
     return 0;
@@ -165,30 +173,16 @@
     return 0;
   }
 
-  uint8_t *p = to;
+  to[0] = 0;
+  to[1] = 2;
 
-  *(p++) = 0;
-  *(p++) = 2; /* Public Key BT (Block Type) */
-
-  /* pad out with non-zero random data */
-  j = to_len - 3 - from_len;
-
-  if (!RAND_bytes(p, j)) {
+  size_t padding_len = to_len - 3 - from_len;
+  if (!rand_nonzero(to + 2, padding_len)) {
     return 0;
   }
 
-  for (i = 0; i < j; i++) {
-    while (*p == 0) {
-      if (!RAND_bytes(p, 1)) {
-        return 0;
-      }
-    }
-    p++;
-  }
-
-  *(p++) = 0;
-
-  OPENSSL_memcpy(p, from, from_len);
+  to[2 + padding_len] = 0;
+  OPENSSL_memcpy(to + to_len - from_len, from, from_len);
   return 1;
 }
 
@@ -258,8 +252,8 @@
   return (int)msg_len;
 }
 
-int RSA_padding_add_none(uint8_t *to, unsigned to_len, const uint8_t *from,
-                         unsigned from_len) {
+int RSA_padding_add_none(uint8_t *to, size_t to_len, const uint8_t *from,
+                         size_t from_len) {
   if (from_len > to_len) {
     OPENSSL_PUT_ERROR(RSA, RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE);
     return 0;
@@ -317,15 +311,10 @@
   return ret;
 }
 
-int RSA_padding_add_PKCS1_OAEP_mgf1(uint8_t *to, unsigned to_len,
-                                    const uint8_t *from, unsigned from_len,
-                                    const uint8_t *param, unsigned param_len,
+int RSA_padding_add_PKCS1_OAEP_mgf1(uint8_t *to, size_t to_len,
+                                    const uint8_t *from, size_t from_len,
+                                    const uint8_t *param, size_t param_len,
                                     const EVP_MD *md, const EVP_MD *mgf1md) {
-  unsigned i, emlen, mdlen;
-  uint8_t *db, *seed;
-  uint8_t *dbmask = NULL, seedmask[EVP_MAX_MD_SIZE];
-  int ret = 0;
-
   if (md == NULL) {
     md = EVP_sha1();
   }
@@ -333,14 +322,14 @@
     mgf1md = md;
   }
 
-  mdlen = EVP_MD_size(md);
+  size_t mdlen = EVP_MD_size(md);
 
   if (to_len < 2 * mdlen + 2) {
     OPENSSL_PUT_ERROR(RSA, RSA_R_KEY_SIZE_TOO_SMALL);
     return 0;
   }
 
-  emlen = to_len - 1;
+  size_t emlen = to_len - 1;
   if (from_len > emlen - 2 * mdlen - 1) {
     OPENSSL_PUT_ERROR(RSA, RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE);
     return 0;
@@ -352,8 +341,8 @@
   }
 
   to[0] = 0;
-  seed = to + 1;
-  db = to + mdlen + 1;
+  uint8_t *seed = to + 1;
+  uint8_t *db = to + mdlen + 1;
 
   if (!EVP_Digest(param, param_len, db, NULL, md, NULL)) {
     return 0;
@@ -365,23 +354,25 @@
     return 0;
   }
 
-  dbmask = OPENSSL_malloc(emlen - mdlen);
+  uint8_t *dbmask = OPENSSL_malloc(emlen - mdlen);
   if (dbmask == NULL) {
     OPENSSL_PUT_ERROR(RSA, ERR_R_MALLOC_FAILURE);
     return 0;
   }
 
+  int ret = 0;
   if (!PKCS1_MGF1(dbmask, emlen - mdlen, seed, mdlen, mgf1md)) {
     goto out;
   }
-  for (i = 0; i < emlen - mdlen; i++) {
+  for (size_t i = 0; i < emlen - mdlen; i++) {
     db[i] ^= dbmask[i];
   }
 
+  uint8_t seedmask[EVP_MAX_MD_SIZE];
   if (!PKCS1_MGF1(seedmask, mdlen, db, emlen - mdlen, mgf1md)) {
     goto out;
   }
-  for (i = 0; i < mdlen; i++) {
+  for (size_t i = 0; i < mdlen; i++) {
     seed[i] ^= seedmask[i];
   }
   ret = 1;
@@ -486,7 +477,7 @@
   return -1;
 }
 
-static const unsigned char zeroes[] = {0,0,0,0,0,0,0,0};
+static const uint8_t kPSSZeroes[] = {0, 0, 0, 0, 0, 0, 0, 0};
 
 int RSA_verify_PKCS1_PSS_mgf1(RSA *rsa, const uint8_t *mHash,
                               const EVP_MD *Hash, const EVP_MD *mgf1Hash,
@@ -567,16 +558,10 @@
     goto err;
   }
   if (!EVP_DigestInit_ex(&ctx, Hash, NULL) ||
-      !EVP_DigestUpdate(&ctx, zeroes, sizeof zeroes) ||
-      !EVP_DigestUpdate(&ctx, mHash, hLen)) {
-    goto err;
-  }
-  if (maskedDBLen - i) {
-    if (!EVP_DigestUpdate(&ctx, DB + i, maskedDBLen - i)) {
-      goto err;
-    }
-  }
-  if (!EVP_DigestFinal_ex(&ctx, H_, NULL)) {
+      !EVP_DigestUpdate(&ctx, kPSSZeroes, sizeof(kPSSZeroes)) ||
+      !EVP_DigestUpdate(&ctx, mHash, hLen) ||
+      !EVP_DigestUpdate(&ctx, DB + i, maskedDBLen - i) ||
+      !EVP_DigestFinal_ex(&ctx, H_, NULL)) {
     goto err;
   }
   if (OPENSSL_memcmp(H_, H, hLen)) {
@@ -601,7 +586,6 @@
   size_t maskedDBLen, MSBits, emLen;
   size_t hLen;
   unsigned char *H, *salt = NULL, *p;
-  EVP_MD_CTX ctx;
 
   if (mgf1Hash == NULL) {
     mgf1Hash = Hash;
@@ -660,19 +644,18 @@
   }
   maskedDBLen = emLen - hLen - 1;
   H = EM + maskedDBLen;
+
+  EVP_MD_CTX ctx;
   EVP_MD_CTX_init(&ctx);
-  if (!EVP_DigestInit_ex(&ctx, Hash, NULL) ||
-      !EVP_DigestUpdate(&ctx, zeroes, sizeof zeroes) ||
-      !EVP_DigestUpdate(&ctx, mHash, hLen)) {
-    goto err;
-  }
-  if (sLen && !EVP_DigestUpdate(&ctx, salt, sLen)) {
-    goto err;
-  }
-  if (!EVP_DigestFinal_ex(&ctx, H, NULL)) {
-    goto err;
-  }
+  int digest_ok = EVP_DigestInit_ex(&ctx, Hash, NULL) &&
+                  EVP_DigestUpdate(&ctx, kPSSZeroes, sizeof(kPSSZeroes)) &&
+                  EVP_DigestUpdate(&ctx, mHash, hLen) &&
+                  EVP_DigestUpdate(&ctx, salt, sLen) &&
+                  EVP_DigestFinal_ex(&ctx, H, NULL);
   EVP_MD_CTX_cleanup(&ctx);
+  if (!digest_ok) {
+    goto err;
+  }
 
   /* Generate dbMask in place then perform XOR on it */
   if (!PKCS1_MGF1(EM, maskedDBLen, H, hLen, mgf1Hash)) {
diff --git a/src/crypto/test/gtest_main.cc b/src/crypto/test/gtest_main.cc
index 50970af..ea1135c 100644
--- a/src/crypto/test/gtest_main.cc
+++ b/src/crypto/test/gtest_main.cc
@@ -19,6 +19,13 @@
 #include <openssl/err.h>
 #include <openssl/crypto.h>
 
+#if defined(OPENSSL_WINDOWS)
+OPENSSL_MSVC_PRAGMA(warning(push, 3))
+#include <winsock2.h>
+OPENSSL_MSVC_PRAGMA(warning(pop))
+#endif
+
+
 namespace {
 
 class ErrorTestEventListener : public testing::EmptyTestEventListener {
@@ -41,6 +48,22 @@
 
 int main(int argc, char **argv) {
   CRYPTO_library_init();
+
+#if defined(OPENSSL_WINDOWS)
+  // Initialize Winsock.
+  WORD wsa_version = MAKEWORD(2, 2);
+  WSADATA wsa_data;
+  int wsa_err = WSAStartup(wsa_version, &wsa_data);
+  if (wsa_err != 0) {
+    fprintf(stderr, "WSAStartup failed: %d\n", wsa_err);
+    return 1;
+  }
+  if (wsa_data.wVersion != wsa_version) {
+    fprintf(stderr, "Didn't get expected version: %x\n", wsa_data.wVersion);
+    return 1;
+  }
+#endif
+
   testing::InitGoogleTest(&argc, argv);
   testing::UnitTest::GetInstance()->listeners().Append(
       new ErrorTestEventListener);
diff --git a/src/crypto/test/test_util.h b/src/crypto/test/test_util.h
index 1447bf6..bce34d4 100644
--- a/src/crypto/test/test_util.h
+++ b/src/crypto/test/test_util.h
@@ -34,6 +34,8 @@
 struct Bytes {
   Bytes(const uint8_t *data_arg, size_t len_arg)
       : data(data_arg), len(len_arg) {}
+  Bytes(const char *data_arg, size_t len_arg)
+      : data(reinterpret_cast<const uint8_t *>(data_arg)), len(len_arg) {}
 
   Bytes(const char *str)
       : data(reinterpret_cast<const uint8_t *>(str)), len(strlen(str)) {}
diff --git a/src/crypto/x509/pkcs7.c b/src/crypto/x509/pkcs7.c
index 9e6a52f..dc3ea7d 100644
--- a/src/crypto/x509/pkcs7.c
+++ b/src/crypto/x509/pkcs7.c
@@ -27,6 +27,14 @@
 #include "../bytestring/internal.h"
 
 
+/* 1.2.840.113549.1.7.1 */
+static const uint8_t kPKCS7Data[] = {0x2a, 0x86, 0x48, 0x86, 0xf7,
+                                     0x0d, 0x01, 0x07, 0x01};
+
+/* 1.2.840.113549.1.7.2 */
+static const uint8_t kPKCS7SignedData[] = {0x2a, 0x86, 0x48, 0x86, 0xf7,
+                                           0x0d, 0x01, 0x07, 0x02};
+
 /* pkcs7_parse_header reads the non-certificate/non-CRL prefix of a PKCS#7
  * SignedData blob from |cbs| and sets |*out| to point to the rest of the
  * input. If the input is in BER format, then |*der_bytes| will be set to a
@@ -57,7 +65,8 @@
     goto err;
   }
 
-  if (OBJ_cbs2nid(&content_type) != NID_pkcs7_signed) {
+  if (!CBS_mem_equal(&content_type, kPKCS7SignedData,
+                     sizeof(kPKCS7SignedData))) {
     OPENSSL_PUT_ERROR(X509, X509_R_NOT_PKCS7_SIGNED_DATA);
     goto err;
   }
@@ -81,11 +90,8 @@
   return 1;
 
 err:
-  if (*der_bytes) {
-    OPENSSL_free(*der_bytes);
-    *der_bytes = NULL;
-  }
-
+  OPENSSL_free(*der_bytes);
+  *der_bytes = NULL;
   return 0;
 }
 
@@ -135,9 +141,7 @@
   ret = 1;
 
 err:
-  if (der_bytes) {
-    OPENSSL_free(der_bytes);
-  }
+  OPENSSL_free(der_bytes);
 
   if (!ret) {
     while (sk_X509_num(out_certs) != initial_certs_len) {
@@ -205,9 +209,7 @@
   ret = 1;
 
 err:
-  if (der_bytes) {
-    OPENSSL_free(der_bytes);
-  }
+  OPENSSL_free(der_bytes);
 
   if (!ret) {
     while (sk_X509_CRL_num(out_crls) != initial_crls_len) {
@@ -270,12 +272,13 @@
  * pkcs7_bundle returns one on success or zero on error. */
 static int pkcs7_bundle(CBB *out, int (*cb)(CBB *out, const void *arg),
                         const void *arg) {
-  CBB outer_seq, wrapped_seq, seq, version_bytes, digest_algos_set,
+  CBB outer_seq, oid, wrapped_seq, seq, version_bytes, digest_algos_set,
       content_info;
 
   /* See https://tools.ietf.org/html/rfc2315#section-7 */
   if (!CBB_add_asn1(out, &outer_seq, CBS_ASN1_SEQUENCE) ||
-      !OBJ_nid2cbb(&outer_seq, NID_pkcs7_signed) ||
+      !CBB_add_asn1(&outer_seq, &oid, CBS_ASN1_OBJECT) ||
+      !CBB_add_bytes(&oid, kPKCS7SignedData, sizeof(kPKCS7SignedData)) ||
       !CBB_add_asn1(&outer_seq, &wrapped_seq,
                     CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
       /* See https://tools.ietf.org/html/rfc2315#section-9.1 */
@@ -284,7 +287,8 @@
       !CBB_add_u8(&version_bytes, 1) ||
       !CBB_add_asn1(&seq, &digest_algos_set, CBS_ASN1_SET) ||
       !CBB_add_asn1(&seq, &content_info, CBS_ASN1_SEQUENCE) ||
-      !OBJ_nid2cbb(&content_info, NID_pkcs7_data) ||
+      !CBB_add_asn1(&content_info, &oid, CBS_ASN1_OBJECT) ||
+      !CBB_add_bytes(&oid, kPKCS7Data, sizeof(kPKCS7Data)) ||
       !cb(&seq, arg)) {
     return 0;
   }
diff --git a/src/decrepit/CMakeLists.txt b/src/decrepit/CMakeLists.txt
index 223320d..a3e409b 100644
--- a/src/decrepit/CMakeLists.txt
+++ b/src/decrepit/CMakeLists.txt
@@ -46,4 +46,7 @@
 )
 
 target_link_libraries(decrepit_test crypto decrepit gtest)
+if (WIN32)
+  target_link_libraries(decrepit_test ws2_32)
+endif()
 add_dependencies(all_tests decrepit_test)
diff --git a/src/decrepit/rsa/rsa_decrepit.c b/src/decrepit/rsa/rsa_decrepit.c
index 0d7c5f6..c4ef5b6 100644
--- a/src/decrepit/rsa/rsa_decrepit.c
+++ b/src/decrepit/rsa/rsa_decrepit.c
@@ -95,9 +95,9 @@
   return RSA_verify_PKCS1_PSS_mgf1(rsa, mHash, Hash, NULL, EM, sLen);
 }
 
-int RSA_padding_add_PKCS1_OAEP(uint8_t *to, unsigned to_len,
-                               const uint8_t *from, unsigned from_len,
-                               const uint8_t *param, unsigned param_len) {
+int RSA_padding_add_PKCS1_OAEP(uint8_t *to, size_t to_len,
+                               const uint8_t *from, size_t from_len,
+                               const uint8_t *param, size_t param_len) {
   return RSA_padding_add_PKCS1_OAEP_mgf1(to, to_len, from, from_len, param,
                                          param_len, NULL, NULL);
 }
diff --git a/src/include/openssl/digest.h b/src/include/openssl/digest.h
index 87de3df..2de84f7 100644
--- a/src/include/openssl/digest.h
+++ b/src/include/openssl/digest.h
@@ -283,5 +283,7 @@
 #endif
 
 #define DIGEST_R_INPUT_NOT_INITIALIZED 100
+#define DIGEST_R_DECODE_ERROR 101
+#define DIGEST_R_UNKNOWN_HASH 102
 
 #endif  /* OPENSSL_HEADER_DIGEST_H */
diff --git a/src/include/openssl/pkcs8.h b/src/include/openssl/pkcs8.h
index 70d6f49..d30ea8e 100644
--- a/src/include/openssl/pkcs8.h
+++ b/src/include/openssl/pkcs8.h
@@ -88,6 +88,14 @@
                                        int iterations,
                                        PKCS8_PRIV_KEY_INFO *p8inf);
 
+/* PKCS8_marshal_encrypted_private_key behaves like |PKCS8_encrypt| but encrypts
+ * an |EVP_PKEY| and writes the serialized EncryptedPrivateKeyInfo to |out|. It
+ * returns one on success and zero on error. */
+OPENSSL_EXPORT int PKCS8_marshal_encrypted_private_key(
+    CBB *out, int pbe_nid, const EVP_CIPHER *cipher, const char *pass,
+    size_t pass_len, const uint8_t *salt, size_t salt_len, int iterations,
+    const EVP_PKEY *pkey);
+
 /* PKCS8_decrypt decrypts and decodes a PKCS8_PRIV_KEY_INFO with PBES1 or PBES2
  * as defined in PKCS #5. Only pbeWithSHAAnd128BitRC4,
  * pbeWithSHAAnd3-KeyTripleDES-CBC and pbeWithSHA1And40BitRC2, and PBES2,
@@ -103,6 +111,13 @@
                                                   const char *pass,
                                                   int pass_len);
 
+/* PKCS8_parse_encrypted_private_key behaves like |PKCS8_decrypt| but it parses
+ * the EncryptedPrivateKeyInfo structure from |cbs| and advances |cbs|. It
+ * returns a newly-allocated |EVP_PKEY| on success and zero on error. */
+OPENSSL_EXPORT EVP_PKEY *PKCS8_parse_encrypted_private_key(CBS *cbs,
+                                                           const char *pass,
+                                                           size_t pass_len);
+
 /* PKCS12_get_key_and_certs parses a PKCS#12 structure from |in|, authenticates
  * and decrypts it using |password|, sets |*out_key| to the included private
  * key and appends the included certificates to |out_certs|. It returns one on
diff --git a/src/include/openssl/rsa.h b/src/include/openssl/rsa.h
index bad3fad..d8f76f0 100644
--- a/src/include/openssl/rsa.h
+++ b/src/include/openssl/rsa.h
@@ -354,8 +354,8 @@
  *
  * It returns one on success or zero on error. */
 OPENSSL_EXPORT int RSA_padding_add_PKCS1_OAEP_mgf1(
-    uint8_t *to, unsigned to_len, const uint8_t *from, unsigned from_len,
-    const uint8_t *param, unsigned param_len, const EVP_MD *md,
+    uint8_t *to, size_t to_len, const uint8_t *from, size_t from_len,
+    const uint8_t *param, size_t param_len, const EVP_MD *md,
     const EVP_MD *mgf1md);
 
 /* RSA_add_pkcs1_prefix builds a version of |msg| prefixed with the DigestInfo
@@ -519,11 +519,11 @@
 /* RSA_padding_add_PKCS1_OAEP acts like |RSA_padding_add_PKCS1_OAEP_mgf1| but
  * the |md| and |mgf1md| parameters of the latter are implicitly set to NULL,
  * which means SHA-1. */
-OPENSSL_EXPORT int RSA_padding_add_PKCS1_OAEP(uint8_t *to, unsigned to_len,
+OPENSSL_EXPORT int RSA_padding_add_PKCS1_OAEP(uint8_t *to, size_t to_len,
                                               const uint8_t *from,
-                                              unsigned from_len,
+                                              size_t from_len,
                                               const uint8_t *param,
-                                              unsigned param_len);
+                                              size_t param_len);
 
 
 struct rsa_meth_st {
diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h
index 11f8ef2..5182df7 100644
--- a/src/include/openssl/ssl.h
+++ b/src/include/openssl/ssl.h
@@ -3078,10 +3078,26 @@
 OPENSSL_EXPORT int SSL_total_renegotiations(const SSL *ssl);
 
 /* SSL_CTX_set_early_data_enabled sets whether early data is allowed to be used
- * with resumptions using |ctx|. WARNING: This is experimental and may cause
- * interoperability failures until fully implemented. */
+ * with resumptions using |ctx|.
+ *
+ * As a server, if the client's early data is accepted, |SSL_do_handshake| will
+ * complete as soon as the ClientHello is processed and server flight sent.
+ * |SSL_write| may be used to send half-RTT data. |SSL_read| will consume early
+ * data and transition to 1-RTT data as appropriate.
+ *
+ * Note early data is replayable by a network attacker. |SSL_in_init| and
+ * |SSL_is_init_finished| will report the handshake is still in progress until
+ * the client's Finished message is received. Callers may use these functions
+ * to defer some processing if desired.
+ *
+ * WARNING: This is experimental and may cause interoperability failures until
+ * fully implemented. */
 OPENSSL_EXPORT void SSL_CTX_set_early_data_enabled(SSL_CTX *ctx, int enabled);
 
+/* SSL_early_data_accepted returns whether early data was accepted on the
+ * handshake performed by |ssl|. */
+OPENSSL_EXPORT int SSL_early_data_accepted(const SSL *ssl);
+
 /* SSL_MAX_CERT_LIST_DEFAULT is the default maximum length, in bytes, of a peer
  * certificate chain. */
 #define SSL_MAX_CERT_LIST_DEFAULT (1024 * 100)
@@ -3144,6 +3160,20 @@
   size_t extensions_len;
 } SSL_CLIENT_HELLO;
 
+/* ssl_select_cert_result_t enumerates the possible results from selecting a
+ * certificate with |select_certificate_cb|. */
+enum ssl_select_cert_result_t {
+  /* ssl_select_cert_success indicates that the certificate selection was
+   * successful. */
+  ssl_select_cert_success = 1,
+  /* ssl_select_cert_retry indicates that the operation could not be
+   * immediately completed and must be reattempted at a later point. */
+  ssl_select_cert_retry = 0,
+  /* ssl_select_cert_error indicates that a fatal error occured and the
+   * handshake should be terminated. */
+  ssl_select_cert_error = -1,
+};
+
 /* SSL_early_callback_ctx_extension_get searches the extensions in
  * |client_hello| for an extension of the given type. If not found, it returns
  * zero. Otherwise it sets |out_data| to point to the extension contents (not
@@ -3156,14 +3186,18 @@
 /* SSL_CTX_set_select_certificate_cb sets a callback that is called before most
  * ClientHello processing and before the decision whether to resume a session
  * is made. The callback may inspect the ClientHello and configure the
- * connection. It may then return one to continue the handshake or zero to
- * pause the handshake to perform an asynchronous operation. If paused,
- * |SSL_get_error| will return |SSL_ERROR_PENDING_CERTIFICATE|.
+ * connection. See |ssl_select_cert_result_t| for details of the return values.
+ *
+ * In the case that a retry is indicated, |SSL_get_error| will return
+ * |SSL_ERROR_PENDING_CERTIFICATE| and the caller should arrange for the
+ * high-level operation on |ssl| to be retried at a later time, which will
+ * result in another call to |cb|.
  *
  * Note: The |SSL_CLIENT_HELLO| is only valid for the duration of the callback
  * and is not valid while the handshake is paused. */
 OPENSSL_EXPORT void SSL_CTX_set_select_certificate_cb(
-    SSL_CTX *ctx, int (*cb)(const SSL_CLIENT_HELLO *));
+    SSL_CTX *ctx,
+    enum ssl_select_cert_result_t (*cb)(const SSL_CLIENT_HELLO *));
 
 /* SSL_CTX_set_dos_protection_cb sets a callback that is called once the
  * resumption decision for a ClientHello has been made. It can return one to
@@ -4112,12 +4146,10 @@
   X509_VERIFY_PARAM *param;
 
   /* select_certificate_cb is called before most ClientHello processing and
-   * before the decision whether to resume a session is made. It may return one
-   * to continue the handshake or zero to cause the handshake loop to return
-   * with an error and cause SSL_get_error to return
-   * SSL_ERROR_PENDING_CERTIFICATE. Note: when the handshake loop is resumed, it
-   * will not call the callback a second time. */
-  int (*select_certificate_cb)(const SSL_CLIENT_HELLO *);
+   * before the decision whether to resume a session is made. See
+   * |ssl_select_cert_result_t| for details of the return values. */
+  enum ssl_select_cert_result_t (*select_certificate_cb)(
+      const SSL_CLIENT_HELLO *);
 
   /* dos_protection_cb is called once the resumption decision for a ClientHello
    * has been made. It returns one to continue the handshake or zero to
@@ -4584,6 +4616,9 @@
 #define SSL_R_CERTIFICATE_AND_PRIVATE_KEY_MISMATCH 274
 #define SSL_R_CANNOT_HAVE_BOTH_PRIVKEY_AND_METHOD 275
 #define SSL_R_TICKET_ENCRYPTION_FAILED 276
+#define SSL_R_ALPN_MISMATCH_ON_EARLY_DATA 277
+#define SSL_R_WRONG_VERSION_ON_EARLY_DATA 278
+#define SSL_R_CHANNEL_ID_ON_EARLY_DATA 279
 #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/CMakeLists.txt b/src/ssl/CMakeLists.txt
index 5b5ea97..3e9cd40 100644
--- a/src/ssl/CMakeLists.txt
+++ b/src/ssl/CMakeLists.txt
@@ -52,4 +52,7 @@
 )
 
 target_link_libraries(ssl_test ssl crypto gtest)
+if (WIN32)
+  target_link_libraries(ssl_test ws2_32)
+endif()
 add_dependencies(all_tests ssl_test)
diff --git a/src/ssl/custom_extensions.c b/src/ssl/custom_extensions.c
index 10fbfc8..ea57c20 100644
--- a/src/ssl/custom_extensions.c
+++ b/src/ssl/custom_extensions.c
@@ -69,6 +69,14 @@
     return 1;
   }
 
+  if (ssl->ctx->enable_early_data) {
+    /* TODO(svaldez): Support Custom Extensions with 0-RTT. For now the caller
+     * is expected not to configure both together.
+     * https://crbug.com/boringssl/173. */
+    OPENSSL_PUT_ERROR(SSL, SSL_R_CUSTOM_EXTENSION_ERROR);
+    return 0;
+  }
+
   for (size_t i = 0; i < sk_SSL_CUSTOM_EXTENSION_num(stack); i++) {
     const SSL_CUSTOM_EXTENSION *ext = sk_SSL_CUSTOM_EXTENSION_value(stack, i);
 
diff --git a/src/ssl/handshake_client.c b/src/ssl/handshake_client.c
index 1feb7d8..7eddd35 100644
--- a/src/ssl/handshake_client.c
+++ b/src/ssl/handshake_client.c
@@ -208,6 +208,18 @@
         }
 
         if (!SSL_is_dtls(ssl) || ssl->d1->send_cookie) {
+          if (hs->early_data_offered) {
+            if (!tls13_init_early_key_schedule(hs) ||
+                !tls13_advance_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)) {
+              ret = -1;
+              goto end;
+            }
+          }
           hs->next_state = SSL3_ST_CR_SRVR_HELLO_A;
         } else {
           hs->next_state = DTLS1_ST_CR_HELLO_VERIFY_REQUEST_A;
@@ -394,6 +406,7 @@
       case SSL3_ST_FALSE_START:
         hs->state = SSL3_ST_CR_SESSION_TICKET_A;
         hs->in_false_start = 1;
+        hs->can_early_write = 1;
         ret = 1;
         goto end;
 
@@ -445,13 +458,21 @@
         }
         break;
 
-      case SSL_ST_TLS13:
-        ret = tls13_handshake(hs);
+      case SSL_ST_TLS13: {
+        int early_return = 0;
+        ret = tls13_handshake(hs, &early_return);
         if (ret <= 0) {
           goto end;
         }
+
+        if (early_return) {
+          ret = 1;
+          goto end;
+        }
+
         hs->state = SSL3_ST_FINISH_CLIENT_HANDSHAKE;
         break;
+      }
 
       case SSL3_ST_FINISH_CLIENT_HANDSHAKE:
         ssl->method->release_current_message(ssl, 1 /* free_buffer */);
@@ -875,6 +896,12 @@
     return 1;
   }
 
+  if (hs->early_data_offered) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_VERSION_ON_EARLY_DATA);
+    al = SSL_AD_PROTOCOL_VERSION;
+    goto f_err;
+  }
+
   ssl_clear_tls13_state(hs);
 
   if (!ssl_check_message_type(ssl, SSL3_MT_SERVER_HELLO)) {
diff --git a/src/ssl/handshake_server.c b/src/ssl/handshake_server.c
index 81e45ef..a1341d6 100644
--- a/src/ssl/handshake_server.c
+++ b/src/ssl/handshake_server.c
@@ -448,13 +448,21 @@
         }
         break;
 
-      case SSL_ST_TLS13:
-        ret = tls13_handshake(hs);
+      case SSL_ST_TLS13: {
+        int early_return = 0;
+        ret = tls13_handshake(hs, &early_return);
         if (ret <= 0) {
           goto end;
         }
+
+        if (early_return) {
+          ret = 1;
+          goto end;
+        }
+
         hs->state = SSL_ST_OK;
         break;
+      }
 
       case SSL_ST_OK:
         ssl->method->release_current_message(ssl, 1 /* free_buffer */);
@@ -812,11 +820,11 @@
   /* Run the early callback. */
   if (ssl->ctx->select_certificate_cb != NULL) {
     switch (ssl->ctx->select_certificate_cb(&client_hello)) {
-      case 0:
+      case ssl_select_cert_retry:
         ssl->rwstate = SSL_CERTIFICATE_SELECTION_PENDING;
         return -1;
 
-      case -1:
+      case ssl_select_cert_error:
         /* Connection rejected. */
         OPENSSL_PUT_ERROR(SSL, SSL_R_CONNECTION_REJECTED);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
diff --git a/src/ssl/internal.h b/src/ssl/internal.h
index 99980d8..ded5ba8 100644
--- a/src/ssl/internal.h
+++ b/src/ssl/internal.h
@@ -865,6 +865,11 @@
  * It returns one on success and zero on error. */
 int tls13_init_key_schedule(SSL_HANDSHAKE *hs);
 
+/* tls13_init_early_key_schedule initializes the handshake hash and key
+ * derivation state from the resumption secret to derive the early secrets. It
+ * returns one on success and zero on error. */
+int tls13_init_early_key_schedule(SSL_HANDSHAKE *hs);
+
 /* tls13_advance_key_schedule incorporates |in| into the key schedule with
  * HKDF-Extract. It returns one on success and zero on error. */
 int tls13_advance_key_schedule(SSL_HANDSHAKE *hs, const uint8_t *in,
@@ -876,6 +881,10 @@
                           const uint8_t *traffic_secret,
                           size_t traffic_secret_len);
 
+/* tls13_derive_early_secrets derives the early traffic secret. It returns one
+ * on success and zero on error. */
+int tls13_derive_early_secrets(SSL_HANDSHAKE *hs);
+
 /* tls13_derive_handshake_secrets derives the handshake traffic secret. It
  * returns one on success and zero on error. */
 int tls13_derive_handshake_secrets(SSL_HANDSHAKE *hs);
@@ -930,6 +939,7 @@
   ssl_hs_channel_id_lookup,
   ssl_hs_private_key_operation,
   ssl_hs_pending_ticket,
+  ssl_hs_read_end_of_early_data,
 };
 
 struct ssl_handshake_st {
@@ -957,10 +967,12 @@
 
   size_t hash_len;
   uint8_t secret[EVP_MAX_MD_SIZE];
+  uint8_t early_traffic_secret[EVP_MAX_MD_SIZE];
   uint8_t client_handshake_secret[EVP_MAX_MD_SIZE];
   uint8_t server_handshake_secret[EVP_MAX_MD_SIZE];
   uint8_t client_traffic_secret_0[EVP_MAX_MD_SIZE];
   uint8_t server_traffic_secret_0[EVP_MAX_MD_SIZE];
+  uint8_t expected_client_finished[EVP_MAX_MD_SIZE];
 
   union {
     /* sent is a bitset where the bits correspond to elements of kExtensions
@@ -1063,10 +1075,6 @@
   uint8_t *key_block;
   uint8_t key_block_len;
 
-  /* session_tickets_sent, in TLS 1.3, is the number of tickets the server has
-   * sent. */
-  uint8_t session_tickets_sent;
-
   /* scts_requested is one if the SCT extension is in the ClientHello. */
   unsigned scts_requested:1;
 
@@ -1100,6 +1108,17 @@
    * Start. The client may write data at this point. */
   unsigned in_false_start:1;
 
+  /* early_data_offered is one if the client sent the early_data extension. */
+  unsigned early_data_offered:1;
+
+  /* can_early_read is one if application data may be read at this point in the
+   * handshake. */
+  unsigned can_early_read:1;
+
+  /* can_early_write is one if application data may be written at this point in
+   * the handshake. */
+  unsigned can_early_write:1;
+
   /* next_proto_neg_seen is one of NPN was negotiated. */
   unsigned next_proto_neg_seen:1;
 
@@ -1128,8 +1147,9 @@
 int ssl_check_message_type(SSL *ssl, int type);
 
 /* tls13_handshake runs the TLS 1.3 handshake. It returns one on success and <=
- * 0 on error. */
-int tls13_handshake(SSL_HANDSHAKE *hs);
+ * 0 on error. It sets |out_early_return| to one if we've completed the
+ * handshake early. */
+int tls13_handshake(SSL_HANDSHAKE *hs, int *out_early_return);
 
 /* The following are implementations of |do_tls13_handshake| for the client and
  * server. */
@@ -1142,7 +1162,11 @@
 
 int tls13_process_certificate(SSL_HANDSHAKE *hs, int allow_anonymous);
 int tls13_process_certificate_verify(SSL_HANDSHAKE *hs);
-int tls13_process_finished(SSL_HANDSHAKE *hs);
+
+/* tls13_process_finished processes the current message as a Finished message
+ * from the peer. If |use_saved_value| is one, the verify_data is compared
+ * against |hs->expected_client_finished| rather than computed fresh. */
+int tls13_process_finished(SSL_HANDSHAKE *hs, int use_saved_value);
 
 int tls13_add_certificate(SSL_HANDSHAKE *hs);
 enum ssl_private_key_result_t tls13_add_certificate_verify(SSL_HANDSHAKE *hs,
@@ -1629,9 +1653,11 @@
   uint8_t write_traffic_secret[EVP_MAX_MD_SIZE];
   uint8_t read_traffic_secret[EVP_MAX_MD_SIZE];
   uint8_t exporter_secret[EVP_MAX_MD_SIZE];
+  uint8_t early_exporter_secret[EVP_MAX_MD_SIZE];
   uint8_t write_traffic_secret_len;
   uint8_t read_traffic_secret_len;
   uint8_t exporter_secret_len;
+  uint8_t early_exporter_secret_len;
 
   /* Connection binding to prevent renegotiation attacks */
   uint8_t previous_client_finished[12];
@@ -1933,6 +1959,9 @@
    * hash of the peer's certificate and then discard it to save memory and
    * session space. Only effective on the server side. */
   unsigned retain_only_sha256_of_client_certs:1;
+
+  /* early_data_accepted is true if early data was accepted by the server. */
+  unsigned early_data_accepted:1;
 };
 
 /* From draft-ietf-tls-tls13-18, used in determining PSK modes. */
@@ -2209,6 +2238,12 @@
  * otherwise. */
 int ssl3_can_false_start(const SSL *ssl);
 
+/* ssl_can_write returns one if |ssl| is allowed to write and zero otherwise. */
+int ssl_can_write(const SSL *ssl);
+
+/* ssl_can_read returns one if |ssl| is allowed to read and zero otherwise. */
+int ssl_can_read(const SSL *ssl);
+
 /* ssl_get_version_range sets |*out_min_version| and |*out_max_version| to the
  * minimum and maximum enabled protocol versions, respectively. */
 int ssl_get_version_range(const SSL *ssl, uint16_t *out_min_version,
diff --git a/src/ssl/s3_both.c b/src/ssl/s3_both.c
index 8fa51e9..7ef400a 100644
--- a/src/ssl/s3_both.c
+++ b/src/ssl/s3_both.c
@@ -153,6 +153,7 @@
   }
 
   OPENSSL_cleanse(hs->secret, sizeof(hs->secret));
+  OPENSSL_cleanse(hs->early_traffic_secret, sizeof(hs->early_traffic_secret));
   OPENSSL_cleanse(hs->client_handshake_secret,
                   sizeof(hs->client_handshake_secret));
   OPENSSL_cleanse(hs->server_handshake_secret,
diff --git a/src/ssl/s3_pkt.c b/src/ssl/s3_pkt.c
index 2f919ca..c2d30ca 100644
--- a/src/ssl/s3_pkt.c
+++ b/src/ssl/s3_pkt.c
@@ -189,7 +189,7 @@
 }
 
 int ssl3_write_app_data(SSL *ssl, const uint8_t *buf, int len) {
-  assert(!SSL_in_init(ssl) || SSL_in_false_start(ssl));
+  assert(ssl_can_write(ssl));
 
   unsigned tot, n, nw;
 
@@ -325,10 +325,11 @@
 
 int ssl3_read_app_data(SSL *ssl, int *out_got_handshake, uint8_t *buf, int len,
                        int peek) {
-  assert(!SSL_in_init(ssl));
-  assert(ssl->s3->initial_handshake_complete);
+  assert(ssl_can_read(ssl));
   *out_got_handshake = 0;
 
+  ssl->method->release_current_message(ssl, 0 /* don't free buffer */);
+
   SSL3_RECORD *rr = &ssl->s3->rrec;
 
   for (;;) {
@@ -345,6 +346,14 @@
     }
 
     if (has_hs_data || rr->type == SSL3_RT_HANDSHAKE) {
+      /* If reading 0-RTT data, reject handshake data. 0-RTT data is terminated
+       * by an alert. */
+      if (SSL_in_init(ssl)) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
+        ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+        return -1;
+      }
+
       /* Post-handshake data prior to TLS 1.3 is always renegotiation, which we
        * never accept as a server. Otherwise |ssl3_get_message| will send
        * |SSL_R_EXCESSIVE_MESSAGE_SIZE|. */
@@ -363,6 +372,24 @@
       return -1;
     }
 
+    /* Handle the end_of_early_data alert. */
+    if (rr->type == SSL3_RT_ALERT &&
+        rr->length == 2 &&
+        rr->data[0] == SSL3_AL_WARNING &&
+        rr->data[1] == TLS1_AD_END_OF_EARLY_DATA &&
+        ssl->server &&
+        ssl->s3->hs != NULL &&
+        ssl->s3->hs->can_early_read &&
+        ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
+      /* Consume the record. */
+      rr->length = 0;
+      ssl_read_buffer_discard(ssl);
+      /* Stop accepting early data. */
+      ssl->s3->hs->can_early_read = 0;
+      *out_got_handshake = 1;
+      return -1;
+    }
+
     if (rr->type != SSL3_RT_APPLICATION_DATA) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_RECORD);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
diff --git a/src/ssl/ssl_cipher.c b/src/ssl/ssl_cipher.c
index 4ee3c12..a72b541 100644
--- a/src/ssl/ssl_cipher.c
+++ b/src/ssl/ssl_cipher.c
@@ -193,6 +193,7 @@
      SSL_HANDSHAKE_MAC_DEFAULT,
     },
 
+#ifdef BORINGSSL_ENABLE_DHE_TLS
     /* Cipher 33 */
     {
      TLS1_TXT_DHE_RSA_WITH_AES_128_SHA,
@@ -203,6 +204,7 @@
      SSL_SHA1,
      SSL_HANDSHAKE_MAC_DEFAULT,
     },
+#endif
 
     /* Cipher 35 */
     {
@@ -215,6 +217,7 @@
      SSL_HANDSHAKE_MAC_DEFAULT,
     },
 
+#ifdef BORINGSSL_ENABLE_DHE_TLS
     /* Cipher 39 */
     {
      TLS1_TXT_DHE_RSA_WITH_AES_256_SHA,
@@ -225,6 +228,7 @@
      SSL_SHA1,
      SSL_HANDSHAKE_MAC_DEFAULT,
     },
+#endif
 
 
     /* TLS v1.2 ciphersuites */
@@ -251,6 +255,7 @@
      SSL_HANDSHAKE_MAC_SHA256,
     },
 
+#ifdef BORINGSSL_ENABLE_DHE_TLS
     /* Cipher 67 */
     {
      TLS1_TXT_DHE_RSA_WITH_AES_128_SHA256,
@@ -272,6 +277,7 @@
      SSL_SHA256,
      SSL_HANDSHAKE_MAC_SHA256,
     },
+#endif
 
     /* PSK cipher suites. */
 
@@ -321,6 +327,7 @@
      SSL_HANDSHAKE_MAC_SHA384,
     },
 
+#ifdef BORINGSSL_ENABLE_DHE_TLS
     /* Cipher 9E */
     {
      TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256,
@@ -342,6 +349,7 @@
      SSL_AEAD,
      SSL_HANDSHAKE_MAC_SHA384,
     },
+#endif
 
     /* TLS 1.3 suites. */
 
@@ -622,9 +630,11 @@
      * e.g. kEDH combines DHE_DSS and DHE_RSA) */
     {"kRSA", SSL_kRSA, ~0u, ~0u, ~0u, 0},
 
+#ifdef BORINGSSL_ENABLE_DHE_TLS
     {"kDHE", SSL_kDHE, ~0u, ~0u, ~0u, 0},
     {"kEDH", SSL_kDHE, ~0u, ~0u, ~0u, 0},
     {"DH", SSL_kDHE, ~0u, ~0u, ~0u, 0},
+#endif
 
     {"kECDHE", SSL_kECDHE, ~0u, ~0u, ~0u, 0},
     {"kEECDH", SSL_kECDHE, ~0u, ~0u, ~0u, 0},
@@ -639,8 +649,10 @@
     {"aPSK", ~0u, SSL_aPSK, ~0u, ~0u, 0},
 
     /* aliases combining key exchange and server authentication */
+#ifdef BORINGSSL_ENABLE_DHE_TLS
     {"DHE", SSL_kDHE, ~0u, ~0u, ~0u, 0},
     {"EDH", SSL_kDHE, ~0u, ~0u, ~0u, 0},
+#endif
     {"ECDHE", SSL_kECDHE, ~0u, ~0u, ~0u, 0},
     {"EECDH", SSL_kECDHE, ~0u, ~0u, ~0u, 0},
     {"RSA", SSL_kRSA, SSL_aRSA, ~SSL_eNULL, ~0u, 0},
diff --git a/src/ssl/ssl_lib.c b/src/ssl/ssl_lib.c
index d16c952..d01f6a2 100644
--- a/src/ssl/ssl_lib.c
+++ b/src/ssl/ssl_lib.c
@@ -613,6 +613,14 @@
   return SSL_do_handshake(ssl);
 }
 
+int ssl_can_write(const SSL *ssl) {
+  return !SSL_in_init(ssl) || ssl->s3->hs->can_early_write;
+}
+
+int ssl_can_read(const SSL *ssl) {
+  return !SSL_in_init(ssl) || ssl->s3->hs->can_early_read;
+}
+
 static int ssl_do_renegotiate(SSL *ssl) {
   /* We do not accept renegotiations as a server or SSL 3.0. SSL 3.0 will be
    * removed entirely in the future and requires retaining more data for
@@ -693,7 +701,7 @@
     /* Complete the current handshake, if any. False Start will cause
      * |SSL_do_handshake| to return mid-handshake, so this may require multiple
      * iterations. */
-    while (SSL_in_init(ssl)) {
+    while (!ssl_can_read(ssl)) {
       int ret = SSL_do_handshake(ssl);
       if (ret < 0) {
         return ret;
@@ -711,6 +719,12 @@
       return ret;
     }
 
+    /* If we received an interrupt in early read (the end_of_early_data alert),
+     * loop again for the handshake to process it. */
+    if (SSL_in_init(ssl)) {
+      continue;
+    }
+
     /* Handle the post-handshake message and try again. */
     if (!ssl_do_post_handshake(ssl)) {
       return -1;
@@ -741,7 +755,7 @@
   }
 
   /* If necessary, complete the handshake implicitly. */
-  if (SSL_in_init(ssl) && !SSL_in_false_start(ssl)) {
+  if (!ssl_can_write(ssl)) {
     int ret = SSL_do_handshake(ssl);
     if (ret < 0) {
       return ret;
@@ -821,6 +835,10 @@
   ctx->enable_early_data = !!enabled;
 }
 
+int SSL_early_data_accepted(const SSL *ssl) {
+  return ssl->early_data_accepted;
+}
+
 static int bio_retry_reason_to_error(int reason) {
   switch (reason) {
     case BIO_RR_CONNECT:
@@ -920,6 +938,7 @@
 
 static int set_min_version(const SSL_PROTOCOL_METHOD *method, uint16_t *out,
                            uint16_t version) {
+  /* Zero is interpreted as the default minimum version. */
   if (version == 0) {
     *out = method->min_version;
     return 1;
@@ -934,6 +953,7 @@
 
 static int set_max_version(const SSL_PROTOCOL_METHOD *method, uint16_t *out,
                            uint16_t version) {
+  /* Zero is interpreted as the default maximum version. */
   if (version == 0) {
     *out = method->max_version;
     /* TODO(svaldez): Enable TLS 1.3 by default once fully implemented. */
@@ -2397,8 +2417,9 @@
 
 int SSL_is_dtls(const SSL *ssl) { return ssl->method->is_dtls; }
 
-void SSL_CTX_set_select_certificate_cb(SSL_CTX *ctx,
-                                       int (*cb)(const SSL_CLIENT_HELLO *)) {
+void SSL_CTX_set_select_certificate_cb(
+    SSL_CTX *ctx,
+    enum ssl_select_cert_result_t (*cb)(const SSL_CLIENT_HELLO *)) {
   ctx->select_certificate_cb = cb;
 }
 
diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc
index 6b150e8..6678b57 100644
--- a/src/ssl/ssl_test.cc
+++ b/src/ssl/ssl_test.cc
@@ -122,14 +122,20 @@
         false,
     },
     // - removes selected ciphers, but preserves their order for future
-    // selections. Select AES_128_GCM, but order the key exchanges RSA, DHE_RSA,
+    // selections. Select AES_128_GCM, but order the key exchanges RSA,
     // ECDHE_RSA.
     {
-        "ALL:-kECDHE:-kDHE:-kRSA:-ALL:"
+        "ALL:-kECDHE:"
+#ifdef BORINGSSL_ENABLE_DHE_TLS
+        "-kDHE:"
+#endif
+        "-kRSA:-ALL:"
         "AESGCM+AES128+aRSA",
         {
             {TLS1_CK_RSA_WITH_AES_128_GCM_SHA256, 0},
+#ifdef BORINGSSL_ENABLE_DHE_TLS
             {TLS1_CK_DHE_RSA_WITH_AES_128_GCM_SHA256, 0},
+#endif
             {TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 0},
         },
         false,
@@ -182,7 +188,10 @@
     {
         // To simplify things, banish all but {ECDHE_RSA,RSA} x
         // {CHACHA20,AES_256_CBC,AES_128_CBC} x SHA1.
-        "!kEDH:!AESGCM:!3DES:!SHA256:!SHA384:"
+#ifdef BORINGSSL_ENABLE_DHE_TLS
+        "!kEDH:"
+#endif
+        "!AESGCM:!3DES:!SHA256:!SHA384:"
         // Order some ciphers backwards by strength.
         "ALL:-CHACHA20:-AES256:-AES128:-ALL:"
         // Select ECDHE ones and sort them by strength. Ties should resolve
@@ -791,9 +800,11 @@
 static const CIPHER_RFC_NAME_TEST kCipherRFCNameTests[] = {
     {SSL3_CK_RSA_DES_192_CBC3_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA"},
     {TLS1_CK_RSA_WITH_AES_128_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA"},
+#ifdef BORINGSSL_ENABLE_DHE_TLS
     {TLS1_CK_DHE_RSA_WITH_AES_256_SHA, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA"},
     {TLS1_CK_DHE_RSA_WITH_AES_256_SHA256,
      "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256"},
+#endif
     {TLS1_CK_ECDHE_RSA_WITH_AES_128_SHA256,
      "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"},
     {TLS1_CK_ECDHE_RSA_WITH_AES_256_SHA384,
@@ -1837,11 +1848,17 @@
 static bool ClientHelloMatches(uint16_t version, const uint8_t *expected,
                                size_t expected_len) {
   bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
+  // Our default cipher list varies by CPU capabilities, so manually place the
+  // ChaCha20 ciphers in front.
+  const char* cipher_list =
+#ifdef BORINGSSL_ENABLE_DHE_TLS
+      "!DHE:CHACHA20:ALL";
+#else
+      "CHACHA20:ALL";
+#endif
   if (!ctx ||
       !SSL_CTX_set_max_proto_version(ctx.get(), version) ||
-      // Our default cipher list varies by CPU capabilities, so manually place
-      // the ChaCha20 ciphers in front.
-      !SSL_CTX_set_strict_cipher_list(ctx.get(), "CHACHA20:ALL")) {
+      !SSL_CTX_set_strict_cipher_list(ctx.get(), cipher_list)) {
     return false;
   }
 
@@ -1887,22 +1904,20 @@
   static const uint8_t kSSL3ClientHello[] = {
     0x16,
     0x03, 0x00,
-    0x00, 0x3f,
+    0x00, 0x3b,
     0x01,
-    0x00, 0x00, 0x3b,
+    0x00, 0x00, 0x37,
     0x03, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00,
-    0x00, 0x14,
+    0x00, 0x10,
     0xc0, 0x09,
     0xc0, 0x13,
-    0x00, 0x33,
     0xc0, 0x0a,
     0xc0, 0x14,
-    0x00, 0x39,
     0x00, 0x2f,
     0x00, 0x35,
     0x00, 0x0a,
@@ -1916,22 +1931,20 @@
   static const uint8_t kTLS1ClientHello[] = {
       0x16,
       0x03, 0x01,
-      0x00, 0x5e,
+      0x00, 0x5a,
       0x01,
-      0x00, 0x00, 0x5a,
+      0x00, 0x00, 0x56,
       0x03, 0x01,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00,
-      0x00, 0x12,
+      0x00, 0x0e,
       0xc0, 0x09,
       0xc0, 0x13,
-      0x00, 0x33,
       0xc0, 0x0a,
       0xc0, 0x14,
-      0x00, 0x39,
       0x00, 0x2f,
       0x00, 0x35,
       0x00, 0x0a,
@@ -1947,22 +1960,20 @@
   static const uint8_t kTLS11ClientHello[] = {
       0x16,
       0x03, 0x01,
-      0x00, 0x5e,
+      0x00, 0x5a,
       0x01,
-      0x00, 0x00, 0x5a,
+      0x00, 0x00, 0x56,
       0x03, 0x02,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00,
-      0x00, 0x12,
+      0x00, 0x0e,
       0xc0, 0x09,
       0xc0, 0x13,
-      0x00, 0x33,
       0xc0, 0x0a,
       0xc0, 0x14,
-      0x00, 0x39,
       0x00, 0x2f,
       0x00, 0x35,
       0x00, 0x0a,
@@ -1982,20 +1993,42 @@
 #endif
 
   static const uint8_t kTLS12ClientHello[] = {
-      0x16, 0x03, 0x01, 0x00, 0x9a, 0x01, 0x00, 0x00, 0x96, 0x03, 0x03, 0x00,
+      0x16,
+      0x03, 0x01,
+      0x00, 0x8e,
+      0x01,
+      0x00, 0x00, 0x8a,
+      0x03, 0x03,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0xcc, 0xa9,
-      0xcc, 0xa8, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e, 0xc0, 0x2c, 0xc0, 0x30,
-      0x00, 0x9f, 0xc0, 0x09, 0xc0, 0x23, 0xc0, 0x13, 0xc0, 0x27, 0x00, 0x33,
-      0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x24, 0xc0, 0x14, 0xc0, 0x28, 0x00, 0x39,
-      0x00, 0x6b, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x3c, 0x00, 0x35,
-      0x00, 0x3d, 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,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x2a,
+      0xcc, 0xa9,
+      0xcc, 0xa8,
+      0xc0, 0x2b,
+      0xc0, 0x2f,
+      0xc0, 0x2c,
+      0xc0, 0x30,
+      0xc0, 0x09,
+      0xc0, 0x23,
+      0xc0, 0x13,
+      0xc0, 0x27,
+      0xc0, 0x0a,
+      0xc0, 0x24,
+      0xc0, 0x14,
+      0xc0, 0x28,
+      0x00, 0x9c,
+      0x00, 0x9d,
+      0x00, 0x2f,
+      0x00, 0x3c,
+      0x00, 0x35,
+      0x00, 0x3d,
+      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,
   };
   if (!ClientHelloMatches(TLS1_2_VERSION, kTLS12ClientHello,
                           sizeof(kTLS12ClientHello))) {
@@ -2111,17 +2144,6 @@
   return SSL_TLSEXT_ERR_OK;
 }
 
-static int SwitchSessionIDContextEarly(const SSL_CLIENT_HELLO *client_hello) {
-  static const uint8_t kContext[] = {3};
-
-  if (!SSL_set_session_id_context(client_hello->ssl, kContext,
-                                  sizeof(kContext))) {
-    return -1;
-  }
-
-  return 1;
-}
-
 static bool TestSessionIDContext(bool is_dtls, const SSL_METHOD *method,
                                  uint16_t version) {
   bssl::UniquePtr<X509> cert = GetTestCertificate();
@@ -2193,8 +2215,18 @@
 
   // Switch the session ID context with the early callback instead.
   SSL_CTX_set_tlsext_servername_callback(server_ctx.get(), nullptr);
-  SSL_CTX_set_select_certificate_cb(server_ctx.get(),
-                                    SwitchSessionIDContextEarly);
+  SSL_CTX_set_select_certificate_cb(
+      server_ctx.get(),
+      [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
+        static const uint8_t kContext[] = {3};
+
+        if (!SSL_set_session_id_context(client_hello->ssl, kContext,
+                                        sizeof(kContext))) {
+          return ssl_select_cert_error;
+        }
+
+        return ssl_select_cert_success;
+      });
 
   if (!ExpectSessionReused(client_ctx.get(), server_ctx.get(), session.get(),
                            false /* expect session not reused */)) {
@@ -2581,12 +2613,13 @@
   ASSERT_TRUE(SSL_CTX_set_max_proto_version(server_ctx.get(), TLS1_3_VERSION));
 
   SSL_CTX_set_select_certificate_cb(
-      server_ctx.get(), [](const SSL_CLIENT_HELLO *client_hello) -> int {
+      server_ctx.get(),
+      [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
         if (!SSL_set_max_proto_version(client_hello->ssl, TLS1_2_VERSION)) {
-          return -1;
+          return ssl_select_cert_error;
         }
 
-        return 1;
+        return ssl_select_cert_success;
       });
 
   bssl::UniquePtr<SSL> client, server;
diff --git a/src/ssl/t1_lib.c b/src/ssl/t1_lib.c
index 759d87b..014432e 100644
--- a/src/ssl/t1_lib.c
+++ b/src/ssl/t1_lib.c
@@ -719,6 +719,7 @@
                                     CBS *contents) {
   SSL *const ssl = hs->ssl;
   if (contents != NULL && ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
+    *out_alert = SSL_AD_ILLEGAL_PARAMETER;
     return 0;
   }
 
@@ -2086,11 +2087,30 @@
  * https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.8 */
 
 static int ext_early_data_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
-  /* TODO(svaldez): Support 0RTT. */
+  SSL *const ssl = hs->ssl;
+  uint16_t session_version;
+  if (ssl->session == NULL ||
+      !ssl->method->version_from_wire(&session_version,
+                                      ssl->session->ssl_version) ||
+      session_version < TLS1_3_VERSION ||
+      ssl->session->ticket_max_early_data == 0 ||
+      hs->received_hello_retry_request ||
+      !ssl->ctx->enable_early_data) {
+    return 1;
+  }
+
+  hs->early_data_offered = 1;
+
+  if (!CBB_add_u16(out, TLSEXT_TYPE_early_data) ||
+      !CBB_add_u16(out, 0) ||
+      !CBB_flush(out)) {
+    return 0;
+  }
+
   return 1;
 }
 
-static int ext_early_data_parse_clienthello(SSL_HANDSHAKE *hs,
+static int ext_early_data_parse_serverhello(SSL_HANDSHAKE *hs,
                                             uint8_t *out_alert, CBS *contents) {
   SSL *const ssl = hs->ssl;
   if (contents == NULL) {
@@ -2102,11 +2122,44 @@
     return 0;
   }
 
-  /* Since we don't currently accept 0-RTT, we have to skip past any early data
-   * the client might have sent. */
-  if (ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
-    ssl->s3->skip_early_data = 1;
+  if (!ssl->s3->session_reused) {
+    *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
+    return 0;
   }
+
+  ssl->early_data_accepted = 1;
+  return 1;
+}
+
+static int ext_early_data_parse_clienthello(SSL_HANDSHAKE *hs,
+                                            uint8_t *out_alert, CBS *contents) {
+  SSL *const ssl = hs->ssl;
+  if (contents == NULL ||
+      ssl3_protocol_version(ssl) < TLS1_3_VERSION) {
+    return 1;
+  }
+
+  if (CBS_len(contents) != 0) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return 0;
+  }
+
+  hs->early_data_offered = 1;
+  return 1;
+}
+
+static int ext_early_data_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
+  if (!hs->ssl->early_data_accepted) {
+    return 1;
+  }
+
+  if (!CBB_add_u16(out, TLSEXT_TYPE_early_data) ||
+      !CBB_add_u16(out, 0) ||
+      !CBB_flush(out)) {
+    return 0;
+  }
+
   return 1;
 }
 
@@ -2597,9 +2650,9 @@
     TLSEXT_TYPE_early_data,
     NULL,
     ext_early_data_add_clienthello,
-    forbid_parse_serverhello,
+    ext_early_data_parse_serverhello,
     ext_early_data_parse_clienthello,
-    dont_add_serverhello,
+    ext_early_data_add_serverhello,
   },
   {
     TLSEXT_TYPE_supported_versions,
diff --git a/src/ssl/test/bssl_shim.cc b/src/ssl/test/bssl_shim.cc
index 804cbbb..21497fd 100644
--- a/src/ssl/test/bssl_shim.cc
+++ b/src/ssl/test/bssl_shim.cc
@@ -108,6 +108,7 @@
   bssl::UniquePtr<SSL_SESSION> new_session;
   bool ticket_decrypt_done = false;
   bool alpn_select_done = false;
+  bool is_resume = false;
   bool early_callback_ready = false;
 };
 
@@ -533,7 +534,8 @@
   return true;
 }
 
-static int SelectCertificateCallback(const SSL_CLIENT_HELLO *client_hello) {
+static enum ssl_select_cert_result_t SelectCertificateCallback(
+    const SSL_CLIENT_HELLO *client_hello) {
   const TestConfig *config = GetTestConfig(client_hello->ssl);
   GetTestState(client_hello->ssl)->early_callback_called = true;
 
@@ -547,7 +549,7 @@
             client_hello, TLSEXT_TYPE_server_name, &extension_data,
             &extension_len)) {
       fprintf(stderr, "Could not find server_name extension.\n");
-      return -1;
+      return ssl_select_cert_error;
     }
 
     CBS_init(&extension, extension_data, extension_len);
@@ -558,7 +560,7 @@
         !CBS_get_u16_length_prefixed(&server_name_list, &host_name) ||
         CBS_len(&server_name_list) != 0) {
       fprintf(stderr, "Could not decode server_name extension.\n");
-      return -1;
+      return ssl_select_cert_error;
     }
 
     if (!CBS_mem_equal(&host_name,
@@ -569,7 +571,7 @@
   }
 
   if (config->fail_early_callback) {
-    return -1;
+    return ssl_select_cert_error;
   }
 
   // Install the certificate in the early callback.
@@ -578,13 +580,13 @@
         GetTestState(client_hello->ssl)->early_callback_ready;
     if (config->async && !early_callback_ready) {
       // Install the certificate asynchronously.
-      return 0;
+      return ssl_select_cert_retry;
     }
     if (!InstallCertificate(client_hello->ssl)) {
-      return -1;
+      return ssl_select_cert_error;
     }
   }
-  return 1;
+  return ssl_select_cert_success;
 }
 
 static bool CheckCertificateRequest(SSL *ssl) {
@@ -763,6 +765,10 @@
 
   *out = (const uint8_t*)config->select_alpn.data();
   *outlen = config->select_alpn.size();
+  if (GetTestState(ssl)->is_resume && config->select_resume_alpn.size() > 0) {
+    *out = (const uint8_t*)config->select_resume_alpn.data();
+    *outlen = config->select_resume_alpn.size();
+  }
   return SSL_TLSEXT_ERR_OK;
 }
 
@@ -1108,7 +1114,8 @@
                                      NULL);
   }
 
-  if (!config->select_alpn.empty() || config->decline_alpn) {
+  if (!config->select_alpn.empty() || !config->select_resume_alpn.empty() ||
+      config->decline_alpn) {
     SSL_CTX_set_alpn_select_cb(ssl_ctx.get(), AlpnSelectCallback, NULL);
   }
 
@@ -1361,7 +1368,9 @@
     return false;
   }
 
-  bool expect_handshake_done = is_resume || !config->false_start;
+  bool expect_handshake_done =
+      (is_resume || !config->false_start) &&
+      !(config->is_server && SSL_early_data_accepted(ssl));
   if (expect_handshake_done != GetTestState(ssl)->handshake_done) {
     fprintf(stderr, "handshake was%s completed\n",
             GetTestState(ssl)->handshake_done ? "" : " not");
@@ -1421,13 +1430,22 @@
     }
   }
 
-  if (!config->expected_alpn.empty()) {
+  std::string expected_alpn = config->expected_alpn;
+  if (is_resume && !config->expected_resume_alpn.empty()) {
+    expected_alpn = config->expected_resume_alpn;
+  }
+  bool expect_no_alpn = (!is_resume && config->expect_no_alpn) ||
+      (is_resume && config->expect_no_resume_alpn);
+  if (expect_no_alpn) {
+    expected_alpn.clear();
+  }
+
+  if (!expected_alpn.empty() || expect_no_alpn) {
     const uint8_t *alpn_proto;
     unsigned alpn_proto_len;
     SSL_get0_alpn_selected(ssl, &alpn_proto, &alpn_proto_len);
-    if (alpn_proto_len != config->expected_alpn.size() ||
-        OPENSSL_memcmp(alpn_proto, config->expected_alpn.data(),
-                       alpn_proto_len) != 0) {
+    if (alpn_proto_len != expected_alpn.size() ||
+        OPENSSL_memcmp(alpn_proto, expected_alpn.data(), alpn_proto_len) != 0) {
       fprintf(stderr, "negotiated alpn proto mismatch\n");
       return false;
     }
@@ -1539,6 +1557,15 @@
     return false;
   }
 
+  if (is_resume) {
+    if ((config->expect_accept_early_data && !SSL_early_data_accepted(ssl)) ||
+        (config->expect_reject_early_data && SSL_early_data_accepted(ssl))) {
+      fprintf(stderr,
+              "Early data was%s accepted, but we expected the opposite\n",
+              SSL_early_data_accepted(ssl) ? "" : " not");
+      return false;
+    }
+  }
 
   if (!config->psk.empty()) {
     if (SSL_get_peer_cert_chain(ssl) != nullptr) {
@@ -1628,6 +1655,10 @@
 static bool DoExchange(bssl::UniquePtr<SSL_SESSION> *out_session,
                        SSL_CTX *ssl_ctx, const TestConfig *config,
                        bool is_resume, SSL_SESSION *session) {
+  if (is_resume && config->enable_resume_early_data) {
+    SSL_CTX_set_early_data_enabled(ssl_ctx, 1);
+  }
+
   bssl::UniquePtr<SSL> ssl(SSL_new(ssl_ctx));
   if (!ssl) {
     return false;
@@ -1638,6 +1669,8 @@
     return false;
   }
 
+  GetTestState(ssl.get())->is_resume = is_resume;
+
   if (config->fallback_scsv &&
       !SSL_set_mode(ssl.get(), SSL_MODE_SEND_FALLBACK_SCSV)) {
     return false;
@@ -1834,20 +1867,16 @@
     return false;
   }
 
-  int ret;
-  if (config->implicit_handshake) {
-    if (config->is_server) {
-      SSL_set_accept_state(ssl.get());
-    } else {
-      SSL_set_connect_state(ssl.get());
-    }
+  if (config->is_server) {
+    SSL_set_accept_state(ssl.get());
   } else {
+    SSL_set_connect_state(ssl.get());
+  }
+
+  int ret;
+  if (!config->implicit_handshake) {
     do {
-      if (config->is_server) {
-        ret = SSL_accept(ssl.get());
-      } else {
-        ret = SSL_connect(ssl.get());
-      }
+      ret = SSL_do_handshake(ssl.get());
     } while (config->async && RetryAsync(ssl.get(), ret));
     if (ret != 1 ||
         !CheckHandshakeProperties(ssl.get(), is_resume)) {
@@ -1978,8 +2007,9 @@
         }
 
         // After a successful read, with or without False Start, the handshake
-        // must be complete.
-        if (!GetTestState(ssl.get())->handshake_done) {
+        // must be complete unless we are doing early data.
+        if (!GetTestState(ssl.get())->handshake_done &&
+            !SSL_early_data_accepted(ssl.get())) {
           fprintf(stderr, "handshake was not completed after SSL_read\n");
           return false;
         }
diff --git a/src/ssl/test/runner/cipher_suites.go b/src/ssl/test/runner/cipher_suites.go
index a2c6fbf..3b2298e 100644
--- a/src/ssl/test/runner/cipher_suites.go
+++ b/src/ssl/test/runner/cipher_suites.go
@@ -48,6 +48,8 @@
 	// client indicates that it supports ECC with a curve and point format
 	// that we're happy with.
 	suiteECDHE = 1 << iota
+	// suiteDHE indicates that the cipher suite involves Diffie-Hellman.
+	suiteDHE
 	// suiteECDSA indicates that the cipher suite involves an ECDSA
 	// signature and therefore may only be selected when the server's
 	// certificate is ECDSA. If this is not set then the cipher suite is
@@ -120,12 +122,12 @@
 	{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, 32, 48, ivLenAES, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12 | suiteSHA384, cipherAES, macSHA384, nil},
 	{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, ivLenAES, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},
 	{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 32, 20, ivLenAES, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil},
-	{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, ivLenAESGCM, dheRSAKA, suiteTLS12, nil, nil, aeadAESGCM},
-	{TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, 32, 0, ivLenAESGCM, dheRSAKA, suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
-	{TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, 16, 32, ivLenAES, dheRSAKA, suiteTLS12, cipherAES, macSHA256, nil},
-	{TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, 32, 32, ivLenAES, dheRSAKA, suiteTLS12, cipherAES, macSHA256, nil},
-	{TLS_DHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, ivLenAES, dheRSAKA, 0, cipherAES, macSHA1, nil},
-	{TLS_DHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, ivLenAES, dheRSAKA, 0, cipherAES, macSHA1, nil},
+	{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, ivLenAESGCM, dheRSAKA, suiteTLS12 | suiteDHE, nil, nil, aeadAESGCM},
+	{TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, 32, 0, ivLenAESGCM, dheRSAKA, suiteTLS12 | suiteSHA384 | suiteDHE, nil, nil, aeadAESGCM},
+	{TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, 16, 32, ivLenAES, dheRSAKA, suiteTLS12 | suiteDHE, cipherAES, macSHA256, nil},
+	{TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, 32, 32, ivLenAES, dheRSAKA, suiteTLS12 | suiteDHE, cipherAES, macSHA256, nil},
+	{TLS_DHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, ivLenAES, dheRSAKA, suiteDHE, cipherAES, macSHA1, nil},
+	{TLS_DHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, ivLenAES, dheRSAKA, suiteDHE, cipherAES, macSHA1, nil},
 	{TLS_RSA_WITH_AES_128_GCM_SHA256, 16, 0, ivLenAESGCM, rsaKA, suiteTLS12, nil, nil, aeadAESGCM},
 	{TLS_RSA_WITH_AES_256_GCM_SHA384, 32, 0, ivLenAESGCM, rsaKA, suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
 	{TLS_RSA_WITH_RC4_128_SHA, 16, 20, noIV, rsaKA, suiteNoDTLS, cipherRC4, macSHA1, nil},
@@ -135,7 +137,7 @@
 	{TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, ivLenAES, rsaKA, 0, cipherAES, macSHA1, nil},
 	{TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, ivLenAES, rsaKA, 0, cipherAES, macSHA1, nil},
 	{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, ivLen3DES, ecdheRSAKA, suiteECDHE, cipher3DES, macSHA1, nil},
-	{TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, ivLen3DES, dheRSAKA, 0, cipher3DES, macSHA1, nil},
+	{TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, ivLen3DES, dheRSAKA, suiteDHE, cipher3DES, macSHA1, nil},
 	{TLS_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, ivLen3DES, rsaKA, 0, cipher3DES, macSHA1, nil},
 	{TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256, 32, 0, ivLenChaCha20Poly1305, ecdhePSKKA, suiteECDHE | suitePSK | suiteTLS12, nil, nil, aeadCHACHA20POLY1305},
 	{TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, 16, 20, ivLenAES, ecdhePSKKA, suiteECDHE | suitePSK, cipherAES, macSHA1, nil},
diff --git a/src/ssl/test/runner/common.go b/src/ssl/test/runner/common.go
index 167e872..95dcbd0 100644
--- a/src/ssl/test/runner/common.go
+++ b/src/ssl/test/runner/common.go
@@ -249,6 +249,7 @@
 	extendedMasterSecret bool                // Whether an extended master secret was used to generate the session
 	sctList              []byte
 	ocspResponse         []byte
+	earlyALPN            string
 	ticketCreationTime   time.Time
 	ticketExpiration     time.Time
 	ticketAgeAdd         uint32
@@ -532,6 +533,10 @@
 	// message.
 	SkipFinished bool
 
+	// SkipEndOfEarlyData causes the implementation to skip the
+	// end_of_early_data alert.
+	SkipEndOfEarlyData bool
+
 	// EarlyChangeCipherSpec causes the client to send an early
 	// ChangeCipherSpec message before the ClientKeyExchange. A value of
 	// zero disables this behavior. One and two configure variants for 0.9.8
@@ -1132,6 +1137,10 @@
 	// send after the ClientHello.
 	SendFakeEarlyDataLength int
 
+	// SendStrayEarlyHandshake, if non-zero, causes the client to send a stray
+	// handshake record before sending end of early data.
+	SendStrayEarlyHandshake bool
+
 	// OmitEarlyDataExtension, if true, causes the early data extension to
 	// be omitted in the ClientHello.
 	OmitEarlyDataExtension bool
@@ -1146,10 +1155,26 @@
 
 	// SendEarlyData causes a TLS 1.3 client to send the provided data
 	// in application data records immediately after the ClientHello,
-	// provided that the client has a PSK that is appropriate for sending
-	// early data and includes that PSK in its ClientHello.
+	// provided that the client offers a TLS 1.3 session. It will do this
+	// whether or not the server advertised early data for the ticket.
 	SendEarlyData [][]byte
 
+	// ExpectEarlyDataAccepted causes a TLS 1.3 client to check that early data
+	// was accepted by the server.
+	ExpectEarlyDataAccepted bool
+
+	// AlwaysAcceptEarlyData causes a TLS 1.3 server to always accept early data
+	// regardless of ALPN mismatch.
+	AlwaysAcceptEarlyData bool
+
+	// AlwaysRejectEarlyData causes a TLS 1.3 server to always reject early data.
+	AlwaysRejectEarlyData bool
+
+	// SendEarlyDataExtension, if true, causes a TLS 1.3 server to send the
+	// early_data extension in EncryptedExtensions, independent of whether
+	// it was accepted.
+	SendEarlyDataExtension bool
+
 	// ExpectEarlyData causes a TLS 1.3 server to read application
 	// data after the ClientHello (assuming the server is able to
 	// derive the key under which the data is encrypted) before it
@@ -1163,9 +1188,9 @@
 	// Finished message.
 	SendHalfRTTData [][]byte
 
-	// ExpectHalfRTTData causes a TLS 1.3 client to read application
-	// data after reading the server's Finished message and before
-	// sending any other handshake messages. It checks that the
+	// ExpectHalfRTTData causes a TLS 1.3 client, if 0-RTT was accepted, to
+	// read application data after reading the server's Finished message and
+	// before sending any subsequent handshake messages. It checks that the
 	// application data it reads matches what is provided in
 	// ExpectHalfRTTData and errors if the number of records or their
 	// content do not match.
diff --git a/src/ssl/test/runner/conn.go b/src/ssl/test/runner/conn.go
index 1bdca84..830977d 100644
--- a/src/ssl/test/runner/conn.go
+++ b/src/ssl/test/runner/conn.go
@@ -1425,6 +1425,43 @@
 	return n + m, c.out.setErrorLocked(err)
 }
 
+func (c *Conn) processTLS13NewSessionTicket(newSessionTicket *newSessionTicketMsg, cipherSuite *cipherSuite) error {
+	if c.config.Bugs.ExpectGREASE && !newSessionTicket.hasGREASEExtension {
+		return errors.New("tls: no GREASE ticket extension found")
+	}
+
+	if c.config.Bugs.ExpectTicketEarlyDataInfo && newSessionTicket.maxEarlyDataSize == 0 {
+		return errors.New("tls: no ticket_early_data_info extension found")
+	}
+
+	if c.config.Bugs.ExpectNoNewSessionTicket {
+		return errors.New("tls: received unexpected NewSessionTicket")
+	}
+
+	if c.config.ClientSessionCache == nil || newSessionTicket.ticketLifetime == 0 {
+		return nil
+	}
+
+	session := &ClientSessionState{
+		sessionTicket:      newSessionTicket.ticket,
+		vers:               c.vers,
+		cipherSuite:        cipherSuite.id,
+		masterSecret:       c.resumptionSecret,
+		serverCertificates: c.peerCertificates,
+		sctList:            c.sctList,
+		ocspResponse:       c.ocspResponse,
+		ticketCreationTime: c.config.time(),
+		ticketExpiration:   c.config.time().Add(time.Duration(newSessionTicket.ticketLifetime) * time.Second),
+		ticketAgeAdd:       newSessionTicket.ticketAgeAdd,
+		maxEarlyDataSize:   newSessionTicket.maxEarlyDataSize,
+		earlyALPN:          c.clientProtocol,
+	}
+
+	cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
+	c.config.ClientSessionCache.Put(cacheKey, session)
+	return nil
+}
+
 func (c *Conn) handlePostHandshakeMessage() error {
 	msg, err := c.readHandshake()
 	if err != nil {
@@ -1449,39 +1486,7 @@
 
 	if c.isClient {
 		if newSessionTicket, ok := msg.(*newSessionTicketMsg); ok {
-			if c.config.Bugs.ExpectGREASE && !newSessionTicket.hasGREASEExtension {
-				return errors.New("tls: no GREASE ticket extension found")
-			}
-
-			if c.config.Bugs.ExpectTicketEarlyDataInfo && newSessionTicket.maxEarlyDataSize == 0 {
-				return errors.New("tls: no ticket_early_data_info extension found")
-			}
-
-			if c.config.Bugs.ExpectNoNewSessionTicket {
-				return errors.New("tls: received unexpected NewSessionTicket")
-			}
-
-			if c.config.ClientSessionCache == nil || newSessionTicket.ticketLifetime == 0 {
-				return nil
-			}
-
-			session := &ClientSessionState{
-				sessionTicket:      newSessionTicket.ticket,
-				vers:               c.vers,
-				cipherSuite:        c.cipherSuite.id,
-				masterSecret:       c.resumptionSecret,
-				serverCertificates: c.peerCertificates,
-				sctList:            c.sctList,
-				ocspResponse:       c.ocspResponse,
-				ticketCreationTime: c.config.time(),
-				ticketExpiration:   c.config.time().Add(time.Duration(newSessionTicket.ticketLifetime) * time.Second),
-				ticketAgeAdd:       newSessionTicket.ticketAgeAdd,
-				maxEarlyDataSize:   newSessionTicket.maxEarlyDataSize,
-			}
-
-			cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
-			c.config.ClientSessionCache.Put(cacheKey, session)
-			return nil
+			return c.processTLS13NewSessionTicket(newSessionTicket, c.cipherSuite)
 		}
 	}
 
@@ -1789,6 +1794,7 @@
 		ticketCreationTime: c.config.time(),
 		ticketExpiration:   c.config.time().Add(time.Duration(m.ticketLifetime) * time.Second),
 		ticketAgeAdd:       uint32(addBuffer[3])<<24 | uint32(addBuffer[2])<<16 | uint32(addBuffer[1])<<8 | uint32(addBuffer[0]),
+		earlyALPN:          []byte(c.clientProtocol),
 	}
 
 	if !c.config.Bugs.SendEmptySessionTicket {
@@ -1798,7 +1804,6 @@
 			return err
 		}
 	}
-
 	c.out.Lock()
 	defer c.out.Unlock()
 	_, err = c.writeRecord(recordTypeHandshake, m.marshal())
diff --git a/src/ssl/test/runner/handshake_client.go b/src/ssl/test/runner/handshake_client.go
index bf38c1a..d73722c 100644
--- a/src/ssl/test/runner/handshake_client.go
+++ b/src/ssl/test/runner/handshake_client.go
@@ -330,7 +330,7 @@
 	}
 
 	var sendEarlyData bool
-	if len(hello.pskIdentities) > 0 && session.maxEarlyDataSize > 0 && c.config.Bugs.SendEarlyData != nil {
+	if len(hello.pskIdentities) > 0 && c.config.Bugs.SendEarlyData != nil {
 		hello.hasEarlyData = true
 		sendEarlyData = true
 	}
@@ -392,7 +392,6 @@
 		finishedHash.Write(helloBytes)
 		earlyTrafficSecret := finishedHash.deriveSecret(earlyTrafficLabel)
 		c.out.useTrafficSecret(session.vers, pskCipherSuite, earlyTrafficSecret, clientWrite)
-
 		for _, earlyData := range c.config.Bugs.SendEarlyData {
 			if _, err := c.writeRecord(recordTypeApplicationData, earlyData); err != nil {
 				return err
@@ -861,20 +860,41 @@
 
 	// If we're expecting 0.5-RTT messages from the server, read them
 	// now.
-	for _, expectedMsg := range c.config.Bugs.ExpectHalfRTTData {
-		if err := c.readRecord(recordTypeApplicationData); err != nil {
-			return err
+	if encryptedExtensions.extensions.hasEarlyData {
+		// BoringSSL will always send two tickets half-RTT when
+		// negotiating 0-RTT.
+		for i := 0; i < shimConfig.HalfRTTTickets; i++ {
+			msg, err := c.readHandshake()
+			if err != nil {
+				return fmt.Errorf("tls: error reading half-RTT ticket: %s", err)
+			}
+			newSessionTicket, ok := msg.(*newSessionTicketMsg)
+			if !ok {
+				return errors.New("tls: expected half-RTT ticket")
+			}
+			if err := c.processTLS13NewSessionTicket(newSessionTicket, hs.suite); err != nil {
+				return err
+			}
 		}
-		if !bytes.Equal(c.input.data[c.input.off:], expectedMsg) {
-			return errors.New("ExpectHalfRTTData: did not get expected message")
+		for _, expectedMsg := range c.config.Bugs.ExpectHalfRTTData {
+			if err := c.readRecord(recordTypeApplicationData); err != nil {
+				return err
+			}
+			if !bytes.Equal(c.input.data[c.input.off:], expectedMsg) {
+				return errors.New("ExpectHalfRTTData: did not get expected message")
+			}
+			c.in.freeBlock(c.input)
+			c.input = nil
 		}
-		c.in.freeBlock(c.input)
-		c.input = nil
 	}
 
 	// Send EndOfEarlyData and then switch write key to handshake
 	// traffic key.
-	if c.out.cipher != nil {
+	if c.out.cipher != nil && !c.config.Bugs.SkipEndOfEarlyData {
+		if c.config.Bugs.SendStrayEarlyHandshake {
+			helloRequest := new(helloRequestMsg)
+			c.writeRecord(recordTypeHandshake, helloRequest.marshal())
+		}
 		c.sendAlert(alertEndOfEarlyData)
 	}
 	c.out.useTrafficSecret(c.vers, hs.suite, clientHandshakeTrafficSecret, clientWrite)
@@ -1336,6 +1356,18 @@
 		c.srtpProtectionProfile = serverExtensions.srtpProtectionProfile
 	}
 
+	if c.vers >= VersionTLS13 && c.didResume {
+		if c.config.Bugs.ExpectEarlyDataAccepted && !serverExtensions.hasEarlyData {
+			c.sendAlert(alertHandshakeFailure)
+			return errors.New("tls: server did not accept early data when expected")
+		}
+
+		if !c.config.Bugs.ExpectEarlyDataAccepted && serverExtensions.hasEarlyData {
+			c.sendAlert(alertHandshakeFailure)
+			return errors.New("tls: server accepted early data when not expected")
+		}
+	}
+
 	return nil
 }
 
diff --git a/src/ssl/test/runner/handshake_server.go b/src/ssl/test/runner/handshake_server.go
index 64edd01..ef144c3 100644
--- a/src/ssl/test/runner/handshake_server.go
+++ b/src/ssl/test/runner/handshake_server.go
@@ -451,7 +451,7 @@
 
 	var pskIndex int
 	foundKEMode := bytes.IndexByte(pskKEModes, pskDHEKEMode) >= 0
-	if foundKEMode {
+	if foundKEMode && !config.SessionTicketsDisabled {
 		for i, pskIdentity := range pskIdentities {
 			// TODO(svaldez): Check the obfuscatedTicketAge before accepting 0-RTT.
 			sessionState, ok := c.decryptTicket(pskIdentity.ticket)
@@ -579,6 +579,10 @@
 		c.writeRecord(recordTypeHandshake, helloRetryRequest.marshal())
 		c.flushHandshake()
 
+		if hs.clientHello.hasEarlyData {
+			c.skipEarlyData = true
+		}
+
 		// Read new ClientHello.
 		newMsg, err := c.readHandshake()
 		if err != nil {
@@ -591,6 +595,10 @@
 		}
 		hs.writeClientHash(newClientHello.marshal())
 
+		if newClientHello.hasEarlyData {
+			return errors.New("tls: EarlyData sent in new ClientHello")
+		}
+
 		applyBugsToClientHello(newClientHello, config)
 
 		// Check that the new ClientHello matches the old ClientHello,
@@ -628,6 +636,7 @@
 			newClientHelloCopy.pskIdentities[i].obfuscatedTicketAge = identity.obfuscatedTicketAge
 		}
 		newClientHelloCopy.pskBinders = oldClientHelloCopy.pskBinders
+		newClientHelloCopy.hasEarlyData = oldClientHelloCopy.hasEarlyData
 
 		if !oldClientHelloCopy.equal(&newClientHelloCopy) {
 			return errors.New("tls: new ClientHello does not match")
@@ -650,10 +659,13 @@
 	}
 
 	// Decide whether or not to accept early data.
-	// TODO(nharper): This does not check that ALPN or SNI matches.
-	if hs.clientHello.hasEarlyData {
-		if !sendHelloRetryRequest && hs.sessionState != nil {
-			encryptedExtensions.extensions.hasEarlyData = true
+	if !sendHelloRetryRequest && hs.clientHello.hasEarlyData {
+		if !config.Bugs.AlwaysRejectEarlyData && hs.sessionState != nil {
+			if c.clientProtocol == string(hs.sessionState.earlyALPN) || config.Bugs.AlwaysAcceptEarlyData {
+				encryptedExtensions.extensions.hasEarlyData = true
+			}
+		}
+		if encryptedExtensions.extensions.hasEarlyData {
 			earlyTrafficSecret := hs.finishedHash.deriveSecret(earlyTrafficLabel)
 			c.in.useTrafficSecret(c.vers, hs.suite, earlyTrafficSecret, clientWrite)
 
@@ -673,6 +685,10 @@
 		}
 	}
 
+	if config.Bugs.SendEarlyDataExtension {
+		encryptedExtensions.extensions.hasEarlyData = true
+	}
+
 	// Resolve ECDHE and compute the handshake secret.
 	if hs.hello.hasKeyShare {
 		// Once a curve has been selected and a key share identified,
@@ -1166,6 +1182,11 @@
 	}
 
 	if len(hs.clientHello.alpnProtocols) > 0 {
+		// We will never offer ALPN as a client on renegotiation
+		// handshakes.
+		if len(c.clientVerify) > 0 {
+			return errors.New("tls: offered ALPN on renegotiation")
+		}
 		if proto := c.config.Bugs.ALPNProtocol; proto != nil {
 			serverExtensions.alpnProtocol = *proto
 			serverExtensions.alpnProtocolEmpty = len(*proto) == 0
diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go
index d90485c..3aa2c46 100644
--- a/src/ssl/test/runner/runner.go
+++ b/src/ssl/test/runner/runner.go
@@ -65,6 +65,7 @@
 	looseErrors        = flag.Bool("loose-errors", false, "If true, allow shims to report an untranslated error code.")
 	shimConfigFile     = flag.String("shim-config", "", "A config file to use to configure the tests for this shim.")
 	includeDisabled    = flag.Bool("include-disabled", false, "If true, also runs disabled tests.")
+	includeDHE         = flag.Bool("include-dhe", false, "If true, test DHE ciphersuites.")
 	repeatUntilFailure = flag.Bool("repeat-until-failure", false, "If true, the first selected test will be run repeatedly until failure.")
 )
 
@@ -82,9 +83,16 @@
 	// “:NO_SHARED_CIPHER:” (a BoringSSL error string) to something
 	// like “SSL_ERROR_NO_CYPHER_OVERLAP”.
 	ErrorMap map[string]string
+
+	// HalfRTTTickets is the number of half-RTT tickets the client should
+	// expect before half-RTT data when testing 0-RTT.
+	HalfRTTTickets int
 }
 
-var shimConfig ShimConfiguration
+// Setup shimConfig defaults aligning with BoringSSL.
+var shimConfig ShimConfiguration = ShimConfiguration{
+	HalfRTTTickets: 2,
+}
 
 type testCert int
 
@@ -1108,12 +1116,6 @@
 	{"AES256-GCM", TLS_RSA_WITH_AES_256_GCM_SHA384},
 	{"AES256-SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
 	{"AES256-SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
-	{"DHE-RSA-AES128-GCM", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
-	{"DHE-RSA-AES128-SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
-	{"DHE-RSA-AES128-SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
-	{"DHE-RSA-AES256-GCM", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
-	{"DHE-RSA-AES256-SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
-	{"DHE-RSA-AES256-SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
 	{"ECDHE-ECDSA-AES128-GCM", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
 	{"ECDHE-ECDSA-AES128-SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
 	{"ECDHE-ECDSA-AES128-SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256},
@@ -1982,26 +1984,6 @@
 			expectedLocalError: "tls: peer did not false start: EOF",
 		},
 		{
-			name: "NoFalseStart-DHE_RSA",
-			config: Config{
-				MaxVersion:   VersionTLS12,
-				CipherSuites: []uint16{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
-				NextProtos:   []string{"foo"},
-				Bugs: ProtocolBugs{
-					ExpectFalseStart:          true,
-					AlertBeforeFalseStartTest: alertAccessDenied,
-				},
-			},
-			flags: []string{
-				"-false-start",
-				"-advertise-alpn", "\x03foo",
-			},
-			shimWritesFirst:    true,
-			shouldFail:         true,
-			expectedError:      ":TLSV1_ALERT_ACCESS_DENIED:",
-			expectedLocalError: "tls: peer did not false start: EOF",
-		},
-		{
 			protocol: dtls,
 			name:     "SendSplitAlert-Sync",
 			config: Config{
@@ -2465,6 +2447,29 @@
 	}
 	testCases = append(testCases, basicTests...)
 
+	if *includeDHE {
+		testCases = append(testCases, testCase{
+			name: "NoFalseStart-DHE_RSA",
+			config: Config{
+				MaxVersion:   VersionTLS12,
+				CipherSuites: []uint16{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+				NextProtos:   []string{"foo"},
+				Bugs: ProtocolBugs{
+					ExpectFalseStart:          true,
+					AlertBeforeFalseStartTest: alertAccessDenied,
+				},
+			},
+			flags: []string{
+				"-false-start",
+				"-advertise-alpn", "\x03foo",
+			},
+			shimWritesFirst:    true,
+			shouldFail:         true,
+			expectedError:      ":TLSV1_ALERT_ACCESS_DENIED:",
+			expectedLocalError: "tls: peer did not false start: EOF",
+		})
+	}
+
 	// Test that very large messages can be received.
 	cert := rsaCertificate
 	for i := 0; i < 50; i++ {
@@ -2685,6 +2690,17 @@
 func addCipherSuiteTests() {
 	const bogusCipher = 0xfe00
 
+	if *includeDHE {
+		testCipherSuites = append(testCipherSuites, []testCipherSuite{
+			{"DHE-RSA-AES128-GCM", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+			{"DHE-RSA-AES128-SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+			{"DHE-RSA-AES128-SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+			{"DHE-RSA-AES256-GCM", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+			{"DHE-RSA-AES256-SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+			{"DHE-RSA-AES256-SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+		}...)
+	}
+
 	for _, suite := range testCipherSuites {
 		for _, ver := range tlsVersions {
 			for _, protocol := range []protocol{tls, dtls} {
@@ -2750,53 +2766,55 @@
 		expectedError: ":UNKNOWN_CIPHER_RETURNED:",
 	})
 
-	testCases = append(testCases, testCase{
-		name: "WeakDH",
-		config: Config{
-			MaxVersion:   VersionTLS12,
-			CipherSuites: []uint16{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
-			Bugs: ProtocolBugs{
-				// This is a 1023-bit prime number, generated
-				// with:
-				// openssl gendh 1023 | openssl asn1parse -i
-				DHGroupPrime: bigFromHex("518E9B7930CE61C6E445C8360584E5FC78D9137C0FFDC880B495D5338ADF7689951A6821C17A76B3ACB8E0156AEA607B7EC406EBEDBB84D8376EB8FE8F8BA1433488BEE0C3EDDFD3A32DBB9481980A7AF6C96BFCF490A094CFFB2B8192C1BB5510B77B658436E27C2D4D023FE3718222AB0CA1273995B51F6D625A4944D0DD4B"),
+	if *includeDHE {
+		testCases = append(testCases, testCase{
+			name: "WeakDH",
+			config: Config{
+				MaxVersion:   VersionTLS12,
+				CipherSuites: []uint16{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+				Bugs: ProtocolBugs{
+					// This is a 1023-bit prime number, generated
+					// with:
+					// openssl gendh 1023 | openssl asn1parse -i
+					DHGroupPrime: bigFromHex("518E9B7930CE61C6E445C8360584E5FC78D9137C0FFDC880B495D5338ADF7689951A6821C17A76B3ACB8E0156AEA607B7EC406EBEDBB84D8376EB8FE8F8BA1433488BEE0C3EDDFD3A32DBB9481980A7AF6C96BFCF490A094CFFB2B8192C1BB5510B77B658436E27C2D4D023FE3718222AB0CA1273995B51F6D625A4944D0DD4B"),
+				},
 			},
-		},
-		shouldFail:    true,
-		expectedError: ":BAD_DH_P_LENGTH:",
-	})
+			shouldFail:    true,
+			expectedError: ":BAD_DH_P_LENGTH:",
+		})
 
-	testCases = append(testCases, testCase{
-		name: "SillyDH",
-		config: Config{
-			MaxVersion:   VersionTLS12,
-			CipherSuites: []uint16{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
-			Bugs: ProtocolBugs{
-				// This is a 4097-bit prime number, generated
-				// with:
-				// openssl gendh 4097 | openssl asn1parse -i
-				DHGroupPrime: bigFromHex("01D366FA64A47419B0CD4A45918E8D8C8430F674621956A9F52B0CA592BC104C6E38D60C58F2CA66792A2B7EBDC6F8FFE75AB7D6862C261F34E96A2AEEF53AB7C21365C2E8FB0582F71EB57B1C227C0E55AE859E9904A25EFECD7B435C4D4357BD840B03649D4A1F8037D89EA4E1967DBEEF1CC17A6111C48F12E9615FFF336D3F07064CB17C0B765A012C850B9E3AA7A6984B96D8C867DDC6D0F4AB52042572244796B7ECFF681CD3B3E2E29AAECA391A775BEE94E502FB15881B0F4AC60314EA947C0C82541C3D16FD8C0E09BB7F8F786582032859D9C13187CE6C0CB6F2D3EE6C3C9727C15F14B21D3CD2E02BDB9D119959B0E03DC9E5A91E2578762300B1517D2352FC1D0BB934A4C3E1B20CE9327DB102E89A6C64A8C3148EDFC5A94913933853442FA84451B31FD21E492F92DD5488E0D871AEBFE335A4B92431DEC69591548010E76A5B365D346786E9A2D3E589867D796AA5E25211201D757560D318A87DFB27F3E625BC373DB48BF94A63161C674C3D4265CB737418441B7650EABC209CF675A439BEB3E9D1AA1B79F67198A40CEFD1C89144F7D8BAF61D6AD36F466DA546B4174A0E0CAF5BD788C8243C7C2DDDCC3DB6FC89F12F17D19FBD9B0BC76FE92891CD6BA07BEA3B66EF12D0D85E788FD58675C1B0FBD16029DCC4D34E7A1A41471BDEDF78BF591A8B4E96D88BEC8EDC093E616292BFC096E69A916E8D624B"),
+		testCases = append(testCases, testCase{
+			name: "SillyDH",
+			config: Config{
+				MaxVersion:   VersionTLS12,
+				CipherSuites: []uint16{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+				Bugs: ProtocolBugs{
+					// This is a 4097-bit prime number, generated
+					// with:
+					// openssl gendh 4097 | openssl asn1parse -i
+					DHGroupPrime: bigFromHex("01D366FA64A47419B0CD4A45918E8D8C8430F674621956A9F52B0CA592BC104C6E38D60C58F2CA66792A2B7EBDC6F8FFE75AB7D6862C261F34E96A2AEEF53AB7C21365C2E8FB0582F71EB57B1C227C0E55AE859E9904A25EFECD7B435C4D4357BD840B03649D4A1F8037D89EA4E1967DBEEF1CC17A6111C48F12E9615FFF336D3F07064CB17C0B765A012C850B9E3AA7A6984B96D8C867DDC6D0F4AB52042572244796B7ECFF681CD3B3E2E29AAECA391A775BEE94E502FB15881B0F4AC60314EA947C0C82541C3D16FD8C0E09BB7F8F786582032859D9C13187CE6C0CB6F2D3EE6C3C9727C15F14B21D3CD2E02BDB9D119959B0E03DC9E5A91E2578762300B1517D2352FC1D0BB934A4C3E1B20CE9327DB102E89A6C64A8C3148EDFC5A94913933853442FA84451B31FD21E492F92DD5488E0D871AEBFE335A4B92431DEC69591548010E76A5B365D346786E9A2D3E589867D796AA5E25211201D757560D318A87DFB27F3E625BC373DB48BF94A63161C674C3D4265CB737418441B7650EABC209CF675A439BEB3E9D1AA1B79F67198A40CEFD1C89144F7D8BAF61D6AD36F466DA546B4174A0E0CAF5BD788C8243C7C2DDDCC3DB6FC89F12F17D19FBD9B0BC76FE92891CD6BA07BEA3B66EF12D0D85E788FD58675C1B0FBD16029DCC4D34E7A1A41471BDEDF78BF591A8B4E96D88BEC8EDC093E616292BFC096E69A916E8D624B"),
+				},
 			},
-		},
-		shouldFail:    true,
-		expectedError: ":DH_P_TOO_LONG:",
-	})
+			shouldFail:    true,
+			expectedError: ":DH_P_TOO_LONG:",
+		})
 
-	// This test ensures that Diffie-Hellman public values are padded with
-	// zeros so that they're the same length as the prime. This is to avoid
-	// hitting a bug in yaSSL.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "DHPublicValuePadded",
-		config: Config{
-			MaxVersion:   VersionTLS12,
-			CipherSuites: []uint16{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
-			Bugs: ProtocolBugs{
-				RequireDHPublicValueLen: (1025 + 7) / 8,
+		// This test ensures that Diffie-Hellman public values are padded with
+		// zeros so that they're the same length as the prime. This is to avoid
+		// hitting a bug in yaSSL.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "DHPublicValuePadded",
+			config: Config{
+				MaxVersion:   VersionTLS12,
+				CipherSuites: []uint16{TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+				Bugs: ProtocolBugs{
+					RequireDHPublicValueLen: (1025 + 7) / 8,
+				},
 			},
-		},
-		flags: []string{"-use-sparse-dh-prime"},
-	})
+			flags: []string{"-use-sparse-dh-prime"},
+		})
+	}
 
 	// The server must be tolerant to bogus ciphers.
 	testCases = append(testCases, testCase{
@@ -3383,9 +3401,11 @@
 }
 
 type stateMachineTestConfig struct {
-	protocol                            protocol
-	async                               bool
-	splitHandshake, packHandshakeFlight bool
+	protocol            protocol
+	async               bool
+	splitHandshake      bool
+	packHandshakeFlight bool
+	implicitHandshake   bool
 }
 
 // Adds tests that try to cover the range of the handshake state machine, under
@@ -3399,6 +3419,11 @@
 				async:    async,
 			})
 			addStateMachineCoverageTests(stateMachineTestConfig{
+				protocol:          protocol,
+				async:             async,
+				implicitHandshake: true,
+			})
+			addStateMachineCoverageTests(stateMachineTestConfig{
 				protocol:       protocol,
 				async:          async,
 				splitHandshake: true,
@@ -3449,14 +3474,6 @@
 		resumeSession: true,
 	})
 	tests = append(tests, testCase{
-		name: "Basic-Client-Implicit",
-		config: Config{
-			MaxVersion: VersionTLS12,
-		},
-		flags:         []string{"-implicit-handshake"},
-		resumeSession: true,
-	})
-	tests = append(tests, testCase{
 		testType: serverTest,
 		name:     "Basic-Server",
 		config: Config{
@@ -3480,15 +3497,6 @@
 	})
 	tests = append(tests, testCase{
 		testType: serverTest,
-		name:     "Basic-Server-Implicit",
-		config: Config{
-			MaxVersion: VersionTLS12,
-		},
-		flags:         []string{"-implicit-handshake"},
-		resumeSession: true,
-	})
-	tests = append(tests, testCase{
-		testType: serverTest,
 		name:     "Basic-Server-EarlyCallback",
 		config: Config{
 			MaxVersion: VersionTLS12,
@@ -3551,6 +3559,43 @@
 			// Cover HelloRetryRequest during an ECDHE-PSK resumption.
 			resumeSession: true,
 		})
+
+		// TODO(svaldez): Send data on early data once implemented.
+		tests = append(tests, testCase{
+			testType: clientTest,
+			name:     "TLS13-EarlyData-Client",
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MinVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-early-data-info",
+				"-expect-accept-early-data",
+			},
+		})
+
+		tests = append(tests, testCase{
+			testType: serverTest,
+			name:     "TLS13-EarlyData-Server",
+			config: Config{
+				MaxVersion: VersionTLS13,
+				MinVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					ExpectEarlyDataAccepted: true,
+					ExpectHalfRTTData:       [][]byte{{254, 253, 252, 251}},
+				},
+			},
+			messageCount:  2,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-accept-early-data",
+			},
+		})
 	}
 
 	// TLS client auth.
@@ -4017,22 +4062,6 @@
 			resumeSession:   true,
 		})
 
-		// Client does False Start but doesn't explicitly call
-		// SSL_connect.
-		tests = append(tests, testCase{
-			name: "FalseStart-Implicit",
-			config: Config{
-				MaxVersion:   VersionTLS12,
-				CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-				NextProtos:   []string{"foo"},
-			},
-			flags: []string{
-				"-implicit-handshake",
-				"-false-start",
-				"-advertise-alpn", "\x03foo",
-			},
-		})
-
 		// False Start without session tickets.
 		tests = append(tests, testCase{
 			name: "FalseStart-SessionTicketsDisabled",
@@ -4166,22 +4195,25 @@
 			flags: []string{"-check-close-notify"},
 		})
 
-		// Bidirectional shutdown with the shim initiating. The runner,
-		// in the meantime, sends garbage before the close_notify which
-		// the shim must ignore.
-		tests = append(tests, testCase{
-			name: "Shutdown-Shim",
-			config: Config{
-				MaxVersion: VersionTLS12,
-				Bugs: ProtocolBugs{
-					ExpectCloseNotify: true,
+		if !config.implicitHandshake {
+			// Bidirectional shutdown with the shim initiating. The runner,
+			// in the meantime, sends garbage before the close_notify which
+			// the shim must ignore. This test is disabled under implicit
+			// handshake tests because the shim never reads or writes.
+			tests = append(tests, testCase{
+				name: "Shutdown-Shim",
+				config: Config{
+					MaxVersion: VersionTLS12,
+					Bugs: ProtocolBugs{
+						ExpectCloseNotify: true,
+					},
 				},
-			},
-			shimShutsDown:     true,
-			sendEmptyRecords:  1,
-			sendWarningAlerts: 1,
-			flags:             []string{"-check-close-notify"},
-		})
+				shimShutsDown:     true,
+				sendEmptyRecords:  1,
+				sendWarningAlerts: 1,
+				flags:             []string{"-check-close-notify"},
+			})
+		}
 	} else {
 		// TODO(davidben): DTLS 1.3 will want a similar thing for
 		// HelloRetryRequest.
@@ -4219,6 +4251,10 @@
 			test.name += "-PackHandshakeFlight"
 			test.config.Bugs.PackHandshakeFlight = true
 		}
+		if config.implicitHandshake {
+			test.name += "-ImplicitHandshake"
+			test.flags = append(test.flags, "-implicit-handshake")
+		}
 		testCases = append(testCases, test)
 	}
 }
@@ -5946,8 +5982,8 @@
 
 	// In TLS 1.3, clients may advertise a cipher list which does not
 	// include the selected cipher. Test that we tolerate this. Servers may
-	// resume at another cipher if the PRF matches, but BoringSSL will
-	// always decline.
+	// resume at another cipher if the PRF matches and are not doing 0-RTT, but
+	// BoringSSL will always decline.
 	testCases = append(testCases, testCase{
 		testType:      serverTest,
 		name:          "Resume-Server-UnofferedCipher-TLS13",
@@ -6548,6 +6584,31 @@
 		shouldFail:    true,
 		expectedError: ":SERVER_CERT_CHANGED:",
 	})
+
+	// We do not negotiate ALPN after the initial handshake. This is
+	// error-prone and only risks bugs in consumers.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "Renegotiation-ForbidALPN",
+		config: Config{
+			MaxVersion: VersionTLS12,
+			Bugs: ProtocolBugs{
+				// Forcibly negotiate ALPN on both initial and
+				// renegotiation handshakes. The test stack will
+				// internally check the client does not offer
+				// it.
+				SendALPN: "foo",
+			},
+		},
+		flags: []string{
+			"-advertise-alpn", "\x03foo\x03bar\x03baz",
+			"-expect-alpn", "foo",
+			"-renegotiate-freely",
+		},
+		renegotiate:   1,
+		shouldFail:    true,
+		expectedError: ":UNEXPECTED_EXTENSION:",
+	})
 }
 
 func addDTLSReplayTests() {
@@ -6624,7 +6685,9 @@
 		TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 		TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
 		TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
-		TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+	}
+	if *includeDHE {
+		signingCiphers = append(signingCiphers, TLS_DHE_RSA_WITH_AES_128_CBC_SHA)
 	}
 
 	var allAlgorithms []signatureAlgorithm
@@ -6724,27 +6787,30 @@
 				expectedError: verifyError,
 			})
 
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "ServerAuth-Sign" + suffix,
-				config: Config{
-					MaxVersion:   ver.version,
-					CipherSuites: signingCiphers,
-					VerifySignatureAlgorithms: []signatureAlgorithm{
-						fakeSigAlg1,
-						alg.id,
-						fakeSigAlg2,
+			// No signing cipher for SSL 3.0.
+			if *includeDHE || ver.version > VersionSSL30 {
+				testCases = append(testCases, testCase{
+					testType: serverTest,
+					name:     "ServerAuth-Sign" + suffix,
+					config: Config{
+						MaxVersion:   ver.version,
+						CipherSuites: signingCiphers,
+						VerifySignatureAlgorithms: []signatureAlgorithm{
+							fakeSigAlg1,
+							alg.id,
+							fakeSigAlg2,
+						},
 					},
-				},
-				flags: []string{
-					"-cert-file", path.Join(*resourceDir, getShimCertificate(alg.cert)),
-					"-key-file", path.Join(*resourceDir, getShimKey(alg.cert)),
-					"-enable-all-curves",
-				},
-				shouldFail:                     shouldSignFail,
-				expectedError:                  signError,
-				expectedPeerSignatureAlgorithm: alg.id,
-			})
+					flags: []string{
+						"-cert-file", path.Join(*resourceDir, getShimCertificate(alg.cert)),
+						"-key-file", path.Join(*resourceDir, getShimKey(alg.cert)),
+						"-enable-all-curves",
+					},
+					shouldFail:                     shouldSignFail,
+					expectedError:                  signError,
+					expectedPeerSignatureAlgorithm: alg.id,
+				})
+			}
 
 			testCases = append(testCases, testCase{
 				name: "ServerAuth-Verify" + suffix,
@@ -7815,6 +7881,22 @@
 			flags: []string{flag},
 		})
 
+		// 0-RTT is not currently supported with Custom Extensions.
+		testCases = append(testCases, testCase{
+			testType: testType,
+			name:     "CustomExtensions-" + suffix + "-EarlyData",
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					CustomExtension:         expectedContents,
+					ExpectedCustomExtension: &expectedContents,
+				},
+			},
+			shouldFail:    true,
+			expectedError: ":CUSTOM_EXTENSION_ERROR:",
+			flags:         []string{flag, "-enable-early-data"},
+		})
+
 		// If the parse callback fails, the handshake should also fail.
 		testCases = append(testCases, testCase{
 			testType: testType,
@@ -8186,11 +8268,11 @@
 			MaxVersion: VersionTLS12,
 			CipherSuites: []uint16{
 				TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
-				TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+				TLS_RSA_WITH_AES_128_GCM_SHA256,
 			},
 			CurvePreferences: []CurveID{CurveP224},
 		},
-		expectedCipher: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+		expectedCipher: TLS_RSA_WITH_AES_128_GCM_SHA256,
 	})
 
 	// The client must reject bogus curves and disabled curves.
@@ -8611,7 +8693,8 @@
 				SendTicketAge: 15 * time.Second,
 			},
 		},
-		resumeSession: true,
+		resumeSession:        true,
+		resumeRenewedSession: true,
 		flags: []string{
 			"-resumption-delay", "10",
 			"-expect-ticket-age-skew", "5",
@@ -8626,7 +8709,8 @@
 				SendTicketAge: 5 * time.Second,
 			},
 		},
-		resumeSession: true,
+		resumeSession:        true,
+		resumeRenewedSession: true,
 		flags: []string{
 			"-resumption-delay", "10",
 			"-expect-ticket-age-skew", "-5",
@@ -9900,6 +9984,413 @@
 			},
 		},
 	})
+
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-DataLessEarlyData-Reject-Client",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+		},
+		resumeConfig: &Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			Bugs: ProtocolBugs{
+				AlwaysRejectEarlyData: true,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-enable-early-data",
+			"-expect-early-data-info",
+			"-expect-reject-early-data",
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-DataLessEarlyData-HRR-Client",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+		},
+		resumeConfig: &Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			Bugs: ProtocolBugs{
+				SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-enable-early-data",
+			"-expect-early-data-info",
+			"-expect-reject-early-data",
+		},
+	})
+
+	// The client must check the server does not send the early_data
+	// extension while rejecting the session.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-EarlyDataWithoutResume-Client",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+		},
+		resumeConfig: &Config{
+			MaxVersion:             VersionTLS13,
+			SessionTicketsDisabled: true,
+			Bugs: ProtocolBugs{
+				SendEarlyDataExtension: true,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-enable-early-data",
+			"-expect-early-data-info",
+		},
+		shouldFail:    true,
+		expectedError: ":UNEXPECTED_EXTENSION:",
+	})
+
+	// The client must fail with a dedicated error code if the server
+	// responds with TLS 1.2 when offering 0-RTT.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-EarlyDataVersionDowngrade-Client",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+		},
+		resumeConfig: &Config{
+			MaxVersion: VersionTLS12,
+		},
+		resumeSession: true,
+		flags: []string{
+			"-enable-early-data",
+			"-expect-early-data-info",
+		},
+		shouldFail:    true,
+		expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
+	})
+
+	// Test that the client rejects an (unsolicited) early_data extension if
+	// the server sent an HRR.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-ServerAcceptsEarlyDataOnHRR-Client",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+		},
+		resumeConfig: &Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			Bugs: ProtocolBugs{
+				SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+				SendEarlyDataExtension:      true,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-enable-early-data",
+			"-expect-early-data-info",
+		},
+		shouldFail:    true,
+		expectedError: ":UNEXPECTED_EXTENSION:",
+	})
+
+	fooString := "foo"
+	barString := "bar"
+
+	// Test that the client reports the correct ALPN after a 0-RTT reject
+	// that changed it.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-DataLessEarlyData-ALPNMismatch-Client",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			Bugs: ProtocolBugs{
+				ALPNProtocol: &fooString,
+			},
+		},
+		resumeConfig: &Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			Bugs: ProtocolBugs{
+				ALPNProtocol: &barString,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-advertise-alpn", "\x03foo\x03bar",
+			"-enable-early-data",
+			"-expect-early-data-info",
+			"-expect-reject-early-data",
+			"-expect-alpn", "foo",
+			"-expect-resume-alpn", "bar",
+		},
+	})
+
+	// Test that the client reports the correct ALPN after a 0-RTT reject if
+	// ALPN was omitted from the first connection.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-DataLessEarlyData-ALPNOmitted1-Client",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+		},
+		resumeConfig: &Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			NextProtos:       []string{"foo"},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-advertise-alpn", "\x03foo\x03bar",
+			"-enable-early-data",
+			"-expect-early-data-info",
+			"-expect-reject-early-data",
+			"-expect-no-alpn",
+			"-expect-resume-alpn", "foo",
+		},
+	})
+
+	// Test that the client reports the correct ALPN after a 0-RTT reject if
+	// ALPN was omitted from the second connection.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-DataLessEarlyData-ALPNOmitted2-Client",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			NextProtos:       []string{"foo"},
+		},
+		resumeConfig: &Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+		},
+		resumeSession: true,
+		flags: []string{
+			"-advertise-alpn", "\x03foo\x03bar",
+			"-enable-early-data",
+			"-expect-early-data-info",
+			"-expect-reject-early-data",
+			"-expect-alpn", "foo",
+			"-expect-no-resume-alpn",
+		},
+	})
+
+	// Test that the client enforces ALPN match on 0-RTT accept.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-DataLessEarlyData-BadALPNMismatch-Client",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			Bugs: ProtocolBugs{
+				ALPNProtocol: &fooString,
+			},
+		},
+		resumeConfig: &Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			Bugs: ProtocolBugs{
+				AlwaysAcceptEarlyData: true,
+				ALPNProtocol:          &barString,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-advertise-alpn", "\x03foo\x03bar",
+			"-enable-early-data",
+			"-expect-early-data-info",
+		},
+		shouldFail:    true,
+		expectedError: ":ALPN_MISMATCH_ON_EARLY_DATA:",
+	})
+
+	// Test that the server correctly rejects 0-RTT when the previous
+	// session did not allow early data on resumption.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-EarlyData-NonZeroRTTSession-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+		},
+		resumeConfig: &Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: false,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-enable-resume-early-data",
+			"-expect-reject-early-data",
+		},
+	})
+
+	// Test that we reject early data where ALPN is omitted from the first
+	// connection.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-EarlyData-ALPNOmitted1-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			NextProtos: []string{},
+		},
+		resumeConfig: &Config{
+			MaxVersion: VersionTLS13,
+			NextProtos: []string{"foo"},
+			Bugs: ProtocolBugs{
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: false,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-enable-early-data",
+			"-select-alpn", "",
+			"-select-resume-alpn", "foo",
+		},
+	})
+
+	// Test that we reject early data where ALPN is omitted from the second
+	// connection.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-EarlyData-ALPNOmitted2-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			NextProtos: []string{"foo"},
+		},
+		resumeConfig: &Config{
+			MaxVersion: VersionTLS13,
+			NextProtos: []string{},
+			Bugs: ProtocolBugs{
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: false,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-enable-early-data",
+			"-select-alpn", "foo",
+			"-select-resume-alpn", "",
+		},
+	})
+
+	// Test that we reject early data with mismatched ALPN.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-EarlyData-ALPNMismatch-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			NextProtos: []string{"foo"},
+		},
+		resumeConfig: &Config{
+			MaxVersion: VersionTLS13,
+			NextProtos: []string{"bar"},
+			Bugs: ProtocolBugs{
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: false,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-enable-early-data",
+			"-select-alpn", "foo",
+			"-select-resume-alpn", "bar",
+		},
+	})
+
+	// Test that we fail on early data with Channel ID.
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "TLS13-EarlyData-ChannelID-Client",
+		config: Config{
+			MaxVersion:       VersionTLS13,
+			MaxEarlyDataSize: 16384,
+			RequestChannelID: true,
+		},
+		resumeSession:   true,
+		expectChannelID: true,
+		shouldFail:      true,
+		expectedError:   ":CHANNEL_ID_ON_EARLY_DATA:",
+		flags: []string{
+			"-enable-early-data",
+			"-expect-early-data-info",
+			"-send-channel-id", path.Join(*resourceDir, channelIDKeyFile),
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-EarlyData-ChannelID-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			ChannelID:  channelIDKey,
+			Bugs: ProtocolBugs{
+				SendEarlyData:           [][]byte{{}},
+				ExpectEarlyDataAccepted: false,
+			},
+		},
+		resumeSession:   true,
+		expectChannelID: true,
+		flags: []string{
+			"-enable-early-data",
+			"-expect-reject-early-data",
+			"-expect-channel-id",
+			base64.StdEncoding.EncodeToString(channelIDBytes),
+		},
+	})
+
+	// Test that the server rejects 0-RTT streams without end_of_early_data.
+	// The subsequent records should fail to decrypt.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-EarlyData-SkipEndOfEarlyData",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				ExpectEarlyDataAccepted: true,
+				SkipEndOfEarlyData:      true,
+			},
+		},
+		resumeSession:      true,
+		flags:              []string{"-enable-early-data"},
+		shouldFail:         true,
+		expectedLocalError: "remote error: bad record MAC",
+		expectedError:      ":BAD_DECRYPT:",
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-EarlyData-UnexpectedHandshake-Server",
+		config: Config{
+			MaxVersion: VersionTLS13,
+		},
+		resumeConfig: &Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+				SendStrayEarlyHandshake: true,
+				ExpectEarlyDataAccepted: true},
+		},
+		resumeSession:      true,
+		shouldFail:         true,
+		expectedError:      ":UNEXPECTED_RECORD:",
+		expectedLocalError: "remote error: unexpected message",
+		flags: []string{
+			"-enable-early-data",
+		},
+	})
 }
 
 func addTLS13CipherPreferenceTests() {
diff --git a/src/ssl/test/runner/ticket.go b/src/ssl/test/runner/ticket.go
index 4a4540c..10ac54f 100644
--- a/src/ssl/test/runner/ticket.go
+++ b/src/ssl/test/runner/ticket.go
@@ -25,6 +25,7 @@
 	handshakeHash        []byte
 	certificates         [][]byte
 	extendedMasterSecret bool
+	earlyALPN            []byte
 	ticketCreationTime   time.Time
 	ticketExpiration     time.Time
 	ticketFlags          uint32
@@ -58,6 +59,9 @@
 		msg.addU32(s.ticketAgeAdd)
 	}
 
+	earlyALPN := msg.addU16LengthPrefixed()
+	earlyALPN.addBytes(s.earlyALPN)
+
 	return msg.finish()
 }
 
@@ -138,6 +142,14 @@
 		data = data[4:]
 	}
 
+	earlyALPNLen := int(data[0])<<8 | int(data[1])
+	data = data[2:]
+	if len(data) < earlyALPNLen {
+		return false
+	}
+	s.earlyALPN = data[:earlyALPNLen]
+	data = data[earlyALPNLen:]
+
 	if len(data) > 0 {
 		return false
 	}
diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc
index e581581..7e57543 100644
--- a/src/ssl/test/test_config.cc
+++ b/src/ssl/test/test_config.cc
@@ -85,6 +85,7 @@
   { "-use-ticket-callback", &TestConfig::use_ticket_callback },
   { "-renew-ticket", &TestConfig::renew_ticket },
   { "-enable-early-data", &TestConfig::enable_early_data },
+  { "-enable-resume-early-data", &TestConfig::enable_resume_early_data },
   { "-enable-client-custom-extension",
     &TestConfig::enable_client_custom_extension },
   { "-enable-server-custom-extension",
@@ -123,6 +124,10 @@
     &TestConfig::expect_no_secure_renegotiation },
   { "-expect-session-id", &TestConfig::expect_session_id },
   { "-expect-no-session-id", &TestConfig::expect_no_session_id },
+  { "-expect-accept-early-data", &TestConfig::expect_accept_early_data },
+  { "-expect-reject-early-data", &TestConfig::expect_reject_early_data },
+  { "-expect-no-alpn", &TestConfig::expect_no_alpn },
+  { "-expect-no-resume-alpn", &TestConfig::expect_no_resume_alpn },
 };
 
 const Flag<std::string> kStringFlags[] = {
@@ -137,8 +142,10 @@
   { "-host-name", &TestConfig::host_name },
   { "-advertise-alpn", &TestConfig::advertise_alpn },
   { "-expect-alpn", &TestConfig::expected_alpn },
+  { "-expect-resume-alpn", &TestConfig::expected_resume_alpn },
   { "-expect-advertised-alpn", &TestConfig::expected_advertised_alpn },
   { "-select-alpn", &TestConfig::select_alpn },
+  { "-select-resume-alpn", &TestConfig::select_resume_alpn },
   { "-psk", &TestConfig::psk },
   { "-psk-identity", &TestConfig::psk_identity },
   { "-srtp-profiles", &TestConfig::srtp_profiles },
diff --git a/src/ssl/test/test_config.h b/src/ssl/test/test_config.h
index 7057b48..fadd05e 100644
--- a/src/ssl/test/test_config.h
+++ b/src/ssl/test/test_config.h
@@ -52,8 +52,12 @@
   std::string host_name;
   std::string advertise_alpn;
   std::string expected_alpn;
+  std::string expected_resume_alpn;
+  bool expect_no_alpn = false;
+  bool expect_no_resume_alpn = false;
   std::string expected_advertised_alpn;
   std::string select_alpn;
+  std::string select_resume_alpn;
   bool decline_alpn = false;
   bool expect_session_miss = false;
   bool expect_extended_master_secret = false;
@@ -84,9 +88,12 @@
   bool expect_ticket_renewal = false;
   bool expect_no_session = false;
   bool expect_early_data_info = false;
+  bool expect_accept_early_data = false;
+  bool expect_reject_early_data = false;
   bool use_ticket_callback = false;
   bool renew_ticket = false;
   bool enable_early_data = false;
+  bool enable_resume_early_data = false;
   bool enable_client_custom_extension = false;
   bool enable_server_custom_extension = false;
   bool custom_extension_skip = false;
diff --git a/src/ssl/tls13_both.c b/src/ssl/tls13_both.c
index 6243923..ec67cdc 100644
--- a/src/ssl/tls13_both.c
+++ b/src/ssl/tls13_both.c
@@ -33,7 +33,7 @@
  * without being able to return application data. */
 static const uint8_t kMaxKeyUpdates = 32;
 
-int tls13_handshake(SSL_HANDSHAKE *hs) {
+int tls13_handshake(SSL_HANDSHAKE *hs, int *out_early_return) {
   SSL *const ssl = hs->ssl;
   for (;;) {
     /* Resolve the operation the handshake was waiting on. */
@@ -64,6 +64,16 @@
         break;
       }
 
+      case ssl_hs_read_end_of_early_data: {
+        if (ssl->s3->hs->can_early_read) {
+          /* While we are processing early data, the handshake returns early. */
+          *out_early_return = 1;
+          return 1;
+        }
+        hs->wait = ssl_hs_ok;
+        break;
+      }
+
       case ssl_hs_x509_lookup:
         ssl->rwstate = SSL_X509_LOOKUP;
         hs->wait = ssl_hs_ok;
@@ -402,12 +412,21 @@
   return ret;
 }
 
-int tls13_process_finished(SSL_HANDSHAKE *hs) {
+int tls13_process_finished(SSL_HANDSHAKE *hs, int use_saved_value) {
   SSL *const ssl = hs->ssl;
-  uint8_t verify_data[EVP_MAX_MD_SIZE];
+  uint8_t verify_data_buf[EVP_MAX_MD_SIZE];
+  const uint8_t *verify_data;
   size_t verify_data_len;
-  if (!tls13_finished_mac(hs, verify_data, &verify_data_len, !ssl->server)) {
-    return 0;
+  if (use_saved_value) {
+    assert(ssl->server);
+    verify_data = hs->expected_client_finished;
+    verify_data_len = hs->hash_len;
+  } else {
+    if (!tls13_finished_mac(hs, verify_data_buf, &verify_data_len,
+                            !ssl->server)) {
+      return 0;
+    }
+    verify_data = verify_data_buf;
   }
 
   int finished_ok =
diff --git a/src/ssl/tls13_client.c b/src/ssl/tls13_client.c
index f13a4f7..0d60661 100644
--- a/src/ssl/tls13_client.c
+++ b/src/ssl/tls13_client.c
@@ -37,6 +37,7 @@
   state_process_server_certificate,
   state_process_server_certificate_verify,
   state_process_server_finished,
+  state_send_end_of_early_data,
   state_send_client_certificate,
   state_send_client_certificate_verify,
   state_complete_client_certificate_verify,
@@ -144,7 +145,11 @@
 }
 
 static enum ssl_hs_wait_t do_send_second_client_hello(SSL_HANDSHAKE *hs) {
-  if (!ssl_write_client_hello(hs)) {
+  SSL *const ssl = hs->ssl;
+  /* TODO(svaldez): Ensure that we set can_early_write to false since 0-RTT is
+   * rejected if we receive a HelloRetryRequest. */
+  if (!ssl->method->set_write_state(ssl, NULL) ||
+      !ssl_write_client_hello(hs)) {
     return ssl_hs_error;
   }
 
@@ -254,7 +259,6 @@
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       return ssl_hs_error;
     }
-    ssl_set_session(ssl, NULL);
 
     /* Resumption incorporates fresh key material, so refresh the timeout. */
     ssl_session_renew_timeout(ssl, hs->new_session,
@@ -267,17 +271,6 @@
   hs->new_session->cipher = cipher;
   hs->new_cipher = cipher;
 
-  /* Store the initial negotiated ALPN in the session. */
-  if (ssl->s3->alpn_selected != NULL) {
-    hs->new_session->early_alpn =
-        BUF_memdup(ssl->s3->alpn_selected, ssl->s3->alpn_selected_len);
-    if (hs->new_session->early_alpn == NULL) {
-      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return ssl_hs_error;
-    }
-    hs->new_session->early_alpn_len = ssl->s3->alpn_selected_len;
-  }
-
   /* The PRF hash is now known. Set up the key schedule. */
   if (!tls13_init_key_schedule(hs)) {
     return ssl_hs_error;
@@ -319,7 +312,13 @@
   if (!ssl_hash_current_message(hs) ||
       !tls13_derive_handshake_secrets(hs) ||
       !tls13_set_traffic_key(ssl, evp_aead_open, hs->server_handshake_secret,
-                             hs->hash_len) ||
+                             hs->hash_len)) {
+    return ssl_hs_error;
+  }
+
+  /* If not sending early data, set client traffic keys now so that alerts are
+   * encrypted. */
+  if (!hs->early_data_offered &&
       !tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret,
                              hs->hash_len)) {
     return ssl_hs_error;
@@ -347,6 +346,36 @@
     return ssl_hs_error;
   }
 
+  /* Store the negotiated ALPN in the session. */
+  if (ssl->s3->alpn_selected != NULL) {
+    hs->new_session->early_alpn =
+        BUF_memdup(ssl->s3->alpn_selected, ssl->s3->alpn_selected_len);
+    if (hs->new_session->early_alpn == NULL) {
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
+    hs->new_session->early_alpn_len = ssl->s3->alpn_selected_len;
+  }
+
+  if (ssl->early_data_accepted) {
+    if (ssl->session->cipher != hs->new_session->cipher ||
+        ssl->session->early_alpn_len != ssl->s3->alpn_selected_len ||
+        OPENSSL_memcmp(ssl->session->early_alpn, ssl->s3->alpn_selected,
+                       ssl->s3->alpn_selected_len) != 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_ALPN_MISMATCH_ON_EARLY_DATA);
+      return ssl_hs_error;
+    }
+    if (ssl->s3->tlsext_channel_id_valid) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_CHANNEL_ID_ON_EARLY_DATA);
+      return ssl_hs_error;
+    }
+  }
+
+  /* Release offered session now that it is no longer needed. */
+  if (ssl->s3->session_reused) {
+    ssl_set_session(ssl, NULL);
+  }
+
   if (!ssl_hash_current_message(hs)) {
     return ssl_hs_error;
   }
@@ -441,7 +470,7 @@
 static enum ssl_hs_wait_t do_process_server_finished(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   if (!ssl_check_message_type(ssl, SSL3_MT_FINISHED) ||
-      !tls13_process_finished(hs) ||
+      !tls13_process_finished(hs, 0 /* don't use saved value */) ||
       !ssl_hash_current_message(hs) ||
       /* Update the secret to the master secret and derive traffic keys. */
       !tls13_advance_key_schedule(hs, kZeroes, hs->hash_len) ||
@@ -450,12 +479,32 @@
   }
 
   ssl->method->received_flight(ssl);
+  hs->tls13_state = state_send_end_of_early_data;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_send_end_of_early_data(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  /* TODO(svaldez): Stop sending early data. */
+  if (ssl->early_data_accepted &&
+      !ssl->method->add_alert(ssl, SSL3_AL_WARNING,
+                              TLS1_AD_END_OF_EARLY_DATA)) {
+    return ssl_hs_error;
+  }
+
+  if (hs->early_data_offered &&
+      !tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret,
+                             hs->hash_len)) {
+    return ssl_hs_error;
+  }
+
   hs->tls13_state = state_send_client_certificate;
   return ssl_hs_ok;
 }
 
 static enum ssl_hs_wait_t do_send_client_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+
   /* The peer didn't request a certificate. */
   if (!hs->cert_request) {
     hs->tls13_state = state_complete_second_flight;
@@ -581,6 +630,9 @@
       case state_process_server_finished:
         ret = do_process_server_finished(hs);
         break;
+      case state_send_end_of_early_data:
+        ret = do_send_end_of_early_data(hs);
+        break;
       case state_send_client_certificate:
         ret = do_send_client_certificate(hs);
         break;
diff --git a/src/ssl/tls13_enc.c b/src/ssl/tls13_enc.c
index 412705d..3a7009c 100644
--- a/src/ssl/tls13_enc.c
+++ b/src/ssl/tls13_enc.c
@@ -28,22 +28,43 @@
 #include "internal.h"
 
 
-int tls13_init_key_schedule(SSL_HANDSHAKE *hs) {
-  if (!SSL_TRANSCRIPT_init_hash(&hs->transcript, ssl3_protocol_version(hs->ssl),
-                                hs->new_cipher->algorithm_prf)) {
+static int init_key_schedule(SSL_HANDSHAKE *hs, uint16_t version,
+                              int algorithm_prf) {
+  if (!SSL_TRANSCRIPT_init_hash(&hs->transcript, version, algorithm_prf)) {
     return 0;
   }
 
-
   hs->hash_len = SSL_TRANSCRIPT_digest_len(&hs->transcript);
 
   /* Initialize the secret to the zero key. */
   OPENSSL_memset(hs->secret, 0, hs->hash_len);
 
+  return 1;
+}
+
+int tls13_init_key_schedule(SSL_HANDSHAKE *hs) {
+  if (!init_key_schedule(hs, ssl3_protocol_version(hs->ssl),
+                         hs->new_cipher->algorithm_prf)) {
+    return 0;
+  }
+
   SSL_TRANSCRIPT_free_buffer(&hs->transcript);
   return 1;
 }
 
+int tls13_init_early_key_schedule(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  uint16_t session_version;
+  if (!ssl->method->version_from_wire(&session_version,
+                                      ssl->session->ssl_version) ||
+      !init_key_schedule(hs, session_version,
+                         ssl->session->cipher->algorithm_prf)) {
+    return 0;
+  }
+
+  return 1;
+}
+
 int tls13_advance_key_schedule(SSL_HANDSHAKE *hs, const uint8_t *in,
                                size_t len) {
   return HKDF_extract(hs->secret, &hs->hash_len,
@@ -100,6 +121,13 @@
 int tls13_set_traffic_key(SSL *ssl, enum evp_aead_direction_t direction,
                           const uint8_t *traffic_secret,
                           size_t traffic_secret_len) {
+  const SSL_SESSION *session = SSL_get_session(ssl);
+  uint16_t version;
+  if (!ssl->method->version_from_wire(&version, session->ssl_version)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return 0;
+  }
+
   if (traffic_secret_len > 0xff) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
     return 0;
@@ -108,14 +136,13 @@
   /* Look up cipher suite properties. */
   const EVP_AEAD *aead;
   size_t discard;
-  if (!ssl_cipher_get_evp_aead(&aead, &discard, &discard,
-                               SSL_get_session(ssl)->cipher,
-                               ssl3_protocol_version(ssl))) {
+  if (!ssl_cipher_get_evp_aead(&aead, &discard, &discard, session->cipher,
+                               version)) {
     return 0;
   }
 
   const EVP_MD *digest = ssl_get_handshake_digest(
-      SSL_get_session(ssl)->cipher->algorithm_prf, ssl3_protocol_version(ssl));
+      session->cipher->algorithm_prf, version);
 
   /* Derive the key. */
   size_t key_len = EVP_AEAD_key_length(aead);
@@ -134,8 +161,7 @@
   }
 
   SSL_AEAD_CTX *traffic_aead = SSL_AEAD_CTX_new(
-      direction, ssl3_protocol_version(ssl), SSL_get_session(ssl)->cipher, key,
-      key_len, NULL, 0, iv, iv_len);
+      direction, version, session->cipher, key, key_len, NULL, 0, iv, iv_len);
   if (traffic_aead == NULL) {
     return 0;
   }
@@ -164,6 +190,11 @@
   return 1;
 }
 
+static const char kTLS13LabelExporter[] = "exporter master secret";
+static const char kTLS13LabelEarlyExporter[] = "early exporter master secret";
+
+static const char kTLS13LabelClientEarlyTraffic[] =
+    "client early traffic secret";
 static const char kTLS13LabelClientHandshakeTraffic[] =
     "client handshake traffic secret";
 static const char kTLS13LabelServerHandshakeTraffic[] =
@@ -173,6 +204,18 @@
 static const char kTLS13LabelServerApplicationTraffic[] =
     "server application traffic secret";
 
+int tls13_derive_early_secrets(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  return derive_secret(hs, hs->early_traffic_secret, hs->hash_len,
+                       (const uint8_t *)kTLS13LabelClientEarlyTraffic,
+                       strlen(kTLS13LabelClientEarlyTraffic)) &&
+         ssl_log_secret(ssl, "CLIENT_EARLY_TRAFFIC_SECRET",
+                        hs->early_traffic_secret, hs->hash_len) &&
+         derive_secret(hs, ssl->s3->early_exporter_secret, hs->hash_len,
+                       (const uint8_t *)kTLS13LabelEarlyExporter,
+                       strlen(kTLS13LabelEarlyExporter));
+}
+
 int tls13_derive_handshake_secrets(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   return derive_secret(hs, hs->client_handshake_secret, hs->hash_len,
@@ -187,8 +230,6 @@
                         hs->server_handshake_secret, hs->hash_len);
 }
 
-static const char kTLS13LabelExporter[] = "exporter master secret";
-
 int tls13_derive_application_secrets(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   ssl->s3->exporter_secret_len = hs->hash_len;
@@ -270,13 +311,11 @@
 
 int tls13_finished_mac(SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len,
                        int is_server) {
-  SSL *const ssl = hs->ssl;
-
   const uint8_t *traffic_secret;
-  if (is_server == ssl->server) {
-    traffic_secret = ssl->s3->write_traffic_secret;
+  if (is_server) {
+    traffic_secret = hs->server_handshake_secret;
   } else {
-    traffic_secret = ssl->s3->read_traffic_secret;
+    traffic_secret = hs->client_handshake_secret;
   }
 
   uint8_t context_hash[EVP_MAX_MD_SIZE];
diff --git a/src/ssl/tls13_server.c b/src/ssl/tls13_server.c
index 9c8d1a1..35ee4f7 100644
--- a/src/ssl/tls13_server.c
+++ b/src/ssl/tls13_server.c
@@ -43,6 +43,8 @@
   state_send_server_certificate_verify,
   state_complete_server_certificate_verify,
   state_send_server_finished,
+  state_read_second_client_flight,
+  state_process_end_of_early_data,
   state_process_client_certificate,
   state_process_client_certificate_verify,
   state_process_channel_id,
@@ -134,6 +136,68 @@
   return best;
 }
 
+static int add_new_session_tickets(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  /* TLS 1.3 recommends single-use tickets, so issue multiple tickets in case
+   * the client makes several connections before getting a renewal. */
+  static const int kNumTickets = 2;
+
+  SSL_SESSION *session = hs->new_session;
+  CBB cbb;
+  CBB_zero(&cbb);
+
+  /* Rebase the session timestamp so that it is measured from ticket
+   * issuance. */
+  ssl_session_rebase_time(ssl, session);
+
+  for (int i = 0; i < kNumTickets; i++) {
+    if (!RAND_bytes((uint8_t *)&session->ticket_age_add, 4)) {
+      goto err;
+    }
+    session->ticket_age_add_valid = 1;
+
+    CBB body, ticket, extensions;
+    if (!ssl->method->init_message(ssl, &cbb, &body,
+                                   SSL3_MT_NEW_SESSION_TICKET) ||
+        !CBB_add_u32(&body, session->timeout) ||
+        !CBB_add_u32(&body, session->ticket_age_add) ||
+        !CBB_add_u16_length_prefixed(&body, &ticket) ||
+        !ssl_encrypt_ticket(ssl, &ticket, session) ||
+        !CBB_add_u16_length_prefixed(&body, &extensions)) {
+      goto err;
+    }
+
+    if (ssl->ctx->enable_early_data) {
+      session->ticket_max_early_data = kMaxEarlyDataAccepted;
+
+      CBB early_data_info;
+      if (!CBB_add_u16(&extensions, TLSEXT_TYPE_ticket_early_data_info) ||
+          !CBB_add_u16_length_prefixed(&extensions, &early_data_info) ||
+          !CBB_add_u32(&early_data_info, session->ticket_max_early_data) ||
+          !CBB_flush(&extensions)) {
+        goto err;
+      }
+    }
+
+    /* Add a fake extension. See draft-davidben-tls-grease-01. */
+    if (!CBB_add_u16(&extensions,
+                     ssl_get_grease_value(ssl, ssl_grease_ticket_extension)) ||
+        !CBB_add_u16(&extensions, 0 /* empty */)) {
+      goto err;
+    }
+
+    if (!ssl_add_message_cbb(ssl, &cbb)) {
+      goto err;
+    }
+  }
+
+  return 1;
+
+err:
+  CBB_cleanup(&cbb);
+  return 0;
+}
+
 static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
   /* At this point, most ClientHello extensions have already been processed by
    * the common handshake logic. Resolve the remaining non-PSK parameters. */
@@ -289,6 +353,21 @@
       /* Carry over authentication information from the previous handshake into
        * a fresh session. */
       hs->new_session = SSL_SESSION_dup(session, SSL_SESSION_DUP_AUTH_ONLY);
+
+      if (/* Early data must be acceptable for this ticket. */
+          ssl->ctx->enable_early_data &&
+          session->ticket_max_early_data != 0 &&
+          /* The client must have offered early data. */
+          hs->early_data_offered &&
+          /* Channel ID is incompatible with 0-RTT. */
+          !ssl->s3->tlsext_channel_id_valid &&
+          /* The negotiated ALPN must match the one in the ticket. */
+          ssl->s3->alpn_selected_len == session->early_alpn_len &&
+          OPENSSL_memcmp(ssl->s3->alpn_selected, session->early_alpn,
+                         ssl->s3->alpn_selected_len) == 0) {
+        ssl->early_data_accepted = 1;
+      }
+
       SSL_SESSION_free(session);
       if (hs->new_session == NULL) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
@@ -323,6 +402,7 @@
     }
   }
 
+  /* Store the initial negotiated ALPN in the session. */
   if (ssl->s3->alpn_selected != NULL) {
     hs->new_session->early_alpn =
         BUF_memdup(ssl->s3->alpn_selected, ssl->s3->alpn_selected_len);
@@ -351,12 +431,22 @@
     return ssl_hs_error;
   }
 
+  if (ssl->early_data_accepted) {
+    if (!tls13_derive_early_secrets(hs)) {
+      return ssl_hs_error;
+    }
+  } else if (hs->early_data_offered) {
+    ssl->s3->skip_early_data = 1;
+  }
+
   ssl->method->received_flight(ssl);
 
   /* Resolve ECDHE and incorporate it into the secret. */
   int need_retry;
   if (!resolve_ecdhe_secret(hs, &need_retry, &client_hello)) {
     if (need_retry) {
+      ssl->early_data_accepted = 0;
+      ssl->s3->skip_early_data = 1;
       hs->tls13_state = state_send_hello_retry_request;
       return ssl_hs_ok;
     }
@@ -440,8 +530,6 @@
 
   /* Derive and enable the handshake traffic secrets. */
   if (!tls13_derive_handshake_secrets(hs) ||
-      !tls13_set_traffic_key(ssl, evp_aead_open, hs->client_handshake_secret,
-                             hs->hash_len) ||
       !tls13_set_traffic_key(ssl, evp_aead_seal, hs->server_handshake_secret,
                              hs->hash_len)) {
     goto err;
@@ -543,8 +631,66 @@
     return ssl_hs_error;
   }
 
-  hs->tls13_state = state_process_client_certificate;
-  return ssl_hs_flush_and_read_message;
+  if (ssl->early_data_accepted) {
+    /* If accepting 0-RTT, we send tickets half-RTT. This gets the tickets on
+     * the wire sooner and also avoids triggering a write on |SSL_read| when
+     * processing the client Finished. This requires computing the client
+     * Finished early. See draft-ietf-tls-tls13-18, section 4.5.1. */
+    size_t finished_len;
+    if (!tls13_finished_mac(hs, hs->expected_client_finished, &finished_len,
+                            0 /* client */)) {
+      return ssl_hs_error;
+    }
+
+    if (finished_len != hs->hash_len) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
+
+    /* Feed the predicted Finished into the transcript. This allows us to derive
+     * the resumption secret early and send half-RTT tickets.
+     *
+     * TODO(davidben): This will need to be updated for DTLS 1.3. */
+    assert(!SSL_is_dtls(hs->ssl));
+    uint8_t header[4] = {SSL3_MT_FINISHED, 0, 0, hs->hash_len};
+    if (!SSL_TRANSCRIPT_update(&hs->transcript, header, sizeof(header)) ||
+        !SSL_TRANSCRIPT_update(&hs->transcript, hs->expected_client_finished,
+                               hs->hash_len) ||
+        !tls13_derive_resumption_secret(hs) ||
+        !add_new_session_tickets(hs)) {
+      return ssl_hs_error;
+    }
+  }
+
+  hs->tls13_state = state_read_second_client_flight;
+  return ssl_hs_flush;
+}
+
+static enum ssl_hs_wait_t do_read_second_client_flight(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  if (ssl->early_data_accepted) {
+    if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->early_traffic_secret,
+                               hs->hash_len)) {
+      return ssl_hs_error;
+    }
+    hs->can_early_write = 1;
+    hs->can_early_read = 1;
+    hs->tls13_state = state_process_end_of_early_data;
+    return ssl_hs_read_end_of_early_data;
+  }
+  hs->tls13_state = state_process_end_of_early_data;
+  return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_process_end_of_early_data(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  if (!tls13_set_traffic_key(ssl, evp_aead_open, hs->client_handshake_secret,
+                             hs->hash_len)) {
+    return ssl_hs_error;
+  }
+  hs->tls13_state = ssl->early_data_accepted ? state_process_client_finished
+                                             : state_process_client_certificate;
+  return ssl_hs_read_message;
 }
 
 static enum ssl_hs_wait_t do_process_client_certificate(SSL_HANDSHAKE *hs) {
@@ -610,30 +756,33 @@
 static enum ssl_hs_wait_t do_process_client_finished(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   if (!ssl_check_message_type(ssl, SSL3_MT_FINISHED) ||
-      !tls13_process_finished(hs) ||
-      !ssl_hash_current_message(hs) ||
+      /* If early data was accepted, we've already computed the client Finished
+       * and derived the resumption secret. */
+      !tls13_process_finished(hs, ssl->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_derive_resumption_secret(hs)) {
+                             hs->hash_len)) {
     return ssl_hs_error;
   }
 
   ssl->method->received_flight(ssl);
 
-  /* Rebase the session timestamp so that it is measured from ticket
-   * issuance. */
-  ssl_session_rebase_time(ssl, hs->new_session);
-  hs->tls13_state = state_send_new_session_ticket;
+  if (!ssl->early_data_accepted) {
+    if (!ssl_hash_current_message(hs) ||
+        !tls13_derive_resumption_secret(hs)) {
+      return ssl_hs_error;
+    }
+
+    /* We send post-handshake tickets as part of the handshake in 1-RTT. */
+    hs->tls13_state = state_send_new_session_ticket;
+    return ssl_hs_ok;
+  }
+
+  hs->tls13_state = state_done;
   return ssl_hs_ok;
 }
 
 static enum ssl_hs_wait_t do_send_new_session_ticket(SSL_HANDSHAKE *hs) {
-  /* TLS 1.3 recommends single-use tickets, so issue multiple tickets in case the
-   * client makes several connections before getting a renewal. */
-  static const int kNumTickets = 2;
-
-  SSL *const ssl = hs->ssl;
   /* If the client doesn't accept resumption with PSK_DHE_KE, don't send a
    * session ticket. */
   if (!hs->accept_psk_mode) {
@@ -641,58 +790,12 @@
     return ssl_hs_ok;
   }
 
-  SSL_SESSION *session = hs->new_session;
-  CBB cbb;
-  CBB_zero(&cbb);
-
-  for (int i = 0; i < kNumTickets; i++) {
-    if (!RAND_bytes((uint8_t *)&session->ticket_age_add, 4)) {
-      goto err;
-    }
-    session->ticket_age_add_valid = 1;
-
-    CBB body, ticket, extensions;
-    if (!ssl->method->init_message(ssl, &cbb, &body,
-                                   SSL3_MT_NEW_SESSION_TICKET) ||
-        !CBB_add_u32(&body, session->timeout) ||
-        !CBB_add_u32(&body, session->ticket_age_add) ||
-        !CBB_add_u16_length_prefixed(&body, &ticket) ||
-        !ssl_encrypt_ticket(ssl, &ticket, session) ||
-        !CBB_add_u16_length_prefixed(&body, &extensions)) {
-      goto err;
-    }
-
-    if (ssl->ctx->enable_early_data) {
-      session->ticket_max_early_data = kMaxEarlyDataAccepted;
-
-      CBB early_data_info;
-      if (!CBB_add_u16(&extensions, TLSEXT_TYPE_ticket_early_data_info) ||
-          !CBB_add_u16_length_prefixed(&extensions, &early_data_info) ||
-          !CBB_add_u32(&early_data_info, session->ticket_max_early_data) ||
-          !CBB_flush(&extensions)) {
-        goto err;
-      }
-    }
-
-    /* Add a fake extension. See draft-davidben-tls-grease-01. */
-    if (!CBB_add_u16(&extensions,
-                     ssl_get_grease_value(ssl, ssl_grease_ticket_extension)) ||
-        !CBB_add_u16(&extensions, 0 /* empty */)) {
-      goto err;
-    }
-
-    if (!ssl_add_message_cbb(ssl, &cbb)) {
-      goto err;
-    }
+  if (!add_new_session_tickets(hs)) {
+    return ssl_hs_error;
   }
 
-  hs->session_tickets_sent++;
   hs->tls13_state = state_done;
   return ssl_hs_flush;
-
-err:
-  CBB_cleanup(&cbb);
-  return ssl_hs_error;
 }
 
 enum ssl_hs_wait_t tls13_server_handshake(SSL_HANDSHAKE *hs) {
@@ -720,10 +823,16 @@
       break;
       case state_complete_server_certificate_verify:
         ret = do_send_server_certificate_verify(hs, 0 /* complete */);
-      break;
+        break;
       case state_send_server_finished:
         ret = do_send_server_finished(hs);
         break;
+      case state_read_second_client_flight:
+        ret = do_read_second_client_flight(hs);
+        break;
+      case state_process_end_of_early_data:
+        ret = do_process_end_of_early_data(hs);
+        break;
       case state_process_client_certificate:
         ret = do_process_client_certificate(hs);
         break;
diff --git a/src/ssl/tls_record.c b/src/ssl/tls_record.c
index aafb6f5..0f9720c 100644
--- a/src/ssl/tls_record.c
+++ b/src/ssl/tls_record.c
@@ -327,6 +327,14 @@
   }
 
   if (type == SSL3_RT_ALERT) {
+    /* Return end_of_early_data alerts as-is for the caller to process. */
+    if (CBS_len(out) == 2 &&
+        CBS_data(out)[0] == SSL3_AL_WARNING &&
+        CBS_data(out)[1] == TLS1_AD_END_OF_EARLY_DATA) {
+      *out_type = type;
+      return ssl_open_record_success;
+    }
+
     return ssl_process_alert(ssl, out_alert, CBS_data(out), CBS_len(out));
   }
 
diff --git a/src/tool/client.cc b/src/tool/client.cc
index 6bc8e39..bc17426 100644
--- a/src/tool/client.cc
+++ b/src/tool/client.cc
@@ -111,6 +111,9 @@
       "verification is required.",
     },
     {
+        "-early-data", kBooleanArgument, "Allow early data",
+    },
+    {
      "", kOptionalArgument, "",
     },
 };
@@ -405,6 +408,10 @@
     SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr);
   }
 
+  if (args_map.count("-early-data") != 0) {
+    SSL_CTX_set_early_data_enabled(ctx.get(), 1);
+  }
+
   if (args_map.count("-resume") != 0 &&
       !DoConnection(ctx.get(), args_map, &WaitForSession)) {
     return false;
diff --git a/src/tool/server.cc b/src/tool/server.cc
index 20c913c..1a97dff 100644
--- a/src/tool/server.cc
+++ b/src/tool/server.cc
@@ -54,6 +54,9 @@
         "The server will continue accepting new sequential connections.",
     },
     {
+        "-early-data", kBooleanArgument, "Allow early data",
+    },
+    {
         "", kOptionalArgument, "",
     },
 };
@@ -223,6 +226,10 @@
     return false;
   }
 
+  if (args_map.count("-early-data") != 0) {
+    SSL_CTX_set_early_data_enabled(ctx.get(), 1);
+  }
+
   bool result = true;
   do {
     int sock = -1;
diff --git a/src/tool/transport_common.cc b/src/tool/transport_common.cc
index 5f1a366..56bde28 100644
--- a/src/tool/transport_common.cc
+++ b/src/tool/transport_common.cc
@@ -292,6 +292,9 @@
     fprintf(stderr, "  SCT list: %s\n", sct_list_len > 0 ? "yes" : "no");
   }
 
+  fprintf(stderr, "  Early data: %s\n",
+          SSL_early_data_accepted(ssl) ? "yes" : "no");
+
   // Print the server cert subject and issuer names.
   bssl::UniquePtr<X509> peer(SSL_get_peer_certificate(ssl));
   if (peer != nullptr) {
diff --git a/src/util/all_tests.json b/src/util/all_tests.json
index d5c6e54..a1ce5f2 100644
--- a/src/util/all_tests.json
+++ b/src/util/all_tests.json
@@ -1,7 +1,6 @@
 [
 	["crypto/aes/aes_test", "crypto/aes/aes_tests.txt"],
 	["crypto/base64/base64_test"],
-	["crypto/bio/bio_test"],
 	["crypto/bn/bn_test", "crypto/bn/bn_tests.txt"],
 	["crypto/bytestring/bytestring_test"],
 	["crypto/cipher/aead_test", "aes-128-gcm", "crypto/cipher/test/aes_128_gcm_tests.txt"],
@@ -25,7 +24,6 @@
 	["crypto/cipher/aead_test", "aes-256-ctr-hmac-sha256", "crypto/cipher/test/aes_256_ctr_hmac_sha256.txt"],
 	["crypto/cipher/cipher_test", "crypto/cipher/test/cipher_tests.txt"],
 	["crypto/cmac/cmac_test"],
-	["crypto/constant_time_test"],
 	["crypto/crypto_test"],
 	["crypto/curve25519/ed25519_test", "crypto/curve25519/ed25519_tests.txt"],
 	["crypto/curve25519/spake25519_test"],