Репозиторий Sisyphus
Последнее обновление: 1 октября 2023 | Пакетов: 18631 | Посещений: 37402967
en ru br
Репозитории ALT
S:2.53.17.1-alt1
5.1: 1.1.18-alt2
4.1: 1.1.16-alt0.M41.1
+updates:1.1.13-alt0.M41.1
4.0: 1.1.12-alt0.M40.1
www.altlinux.org/Changes

Группа :: Сети/WWW
Пакет: seamonkey

 Главная   Изменения   Спек   Патчи   Sources   Загрузить   Gear   Bugs and FR  Repocop 

Патч: 0022-MOZILLA-1666567-land-NSS-NSS_3_58_BETA1-UPGRADE_NSS_.patch
Скачать


diff --git a/mozilla/security/nss/automation/abi-check/expected-report-libnss3.so.txt b/mozilla/security/nss/automation/abi-check/expected-report-libnss3.so.txt
index b4d9ee290a0..3bb07caaabb 100644
--- a/mozilla/security/nss/automation/abi-check/expected-report-libnss3.so.txt
+++ b/mozilla/security/nss/automation/abi-check/expected-report-libnss3.so.txt
@@ -1,6 +1,17 @@
 
-1 Added function:
+12 Added functions:
 
+  [A] 'function SECStatus PK11_HPKE_Deserialize(const HpkeContext*, const PRUint8*, unsigned int, SECKEYPublicKey**)'    {PK11_HPKE_Deserialize@@NSS_3.58}
+  [A] 'function void PK11_HPKE_DestroyContext(HpkeContext*, PRBool)'    {PK11_HPKE_DestroyContext@@NSS_3.58}
+  [A] 'function SECStatus PK11_HPKE_ExportSecret(const HpkeContext*, const SECItem*, unsigned int, PK11SymKey**)'    {PK11_HPKE_ExportSecret@@NSS_3.58}
+  [A] 'function const SECItem* PK11_HPKE_GetEncapPubKey(const HpkeContext*)'    {PK11_HPKE_GetEncapPubKey@@NSS_3.58}
+  [A] 'function HpkeContext* PK11_HPKE_NewContext(HpkeKemId, HpkeKdfId, HpkeAeadId, PK11SymKey*, const SECItem*)'    {PK11_HPKE_NewContext@@NSS_3.58}
+  [A] 'function SECStatus PK11_HPKE_Open(HpkeContext*, const SECItem*, const SECItem*, SECItem**)'    {PK11_HPKE_Open@@NSS_3.58}
+  [A] 'function SECStatus PK11_HPKE_Seal(HpkeContext*, const SECItem*, const SECItem*, SECItem**)'    {PK11_HPKE_Seal@@NSS_3.58}
+  [A] 'function SECStatus PK11_HPKE_Serialize(const SECKEYPublicKey*, PRUint8*, unsigned int*, unsigned int)'    {PK11_HPKE_Serialize@@NSS_3.58}
+  [A] 'function SECStatus PK11_HPKE_SetupR(HpkeContext*, const SECKEYPublicKey*, SECKEYPrivateKey*, const SECItem*, const SECItem*)'    {PK11_HPKE_SetupR@@NSS_3.58}
+  [A] 'function SECStatus PK11_HPKE_SetupS(HpkeContext*, const SECKEYPublicKey*, SECKEYPrivateKey*, SECKEYPublicKey*, const SECItem*)'    {PK11_HPKE_SetupS@@NSS_3.58}
+  [A] 'function SECStatus PK11_HPKE_ValidateParameters(HpkeKemId, HpkeKdfId, HpkeAeadId)'    {PK11_HPKE_ValidateParameters@@NSS_3.58}
   [A] 'function PK11SymKey* PK11_ImportDataKey(PK11SlotInfo*, CK_MECHANISM_TYPE, PK11Origin, CK_ATTRIBUTE_TYPE, SECItem*, void*)'    {PK11_ImportDataKey@@NSS_3.58}
 
 1 Added function:
diff --git a/mozilla/security/nss/automation/taskcluster/graph/src/extend.js b/mozilla/security/nss/automation/taskcluster/graph/src/extend.js
index 122340ed3cd..658f06ab1ff 100644
--- a/mozilla/security/nss/automation/taskcluster/graph/src/extend.js
+++ b/mozilla/security/nss/automation/taskcluster/graph/src/extend.js
@@ -100,11 +100,18 @@ queue.filter(task => {
 
   // Don't run all additional hardware tests on ARM.
   if (task.group == "Cipher" && task.platform == "aarch64" && task.env &&
-      (task.env.NSS_DISABLE_PCLMUL == "1" || task.env.NSS_DISABLE_HW_AES == "1"
+      (task.env.NSS_DISABLE_PCLMUL == "1" || task.env.NSS_DISABLE_SSE4_1 == "1"
        || task.env.NSS_DISABLE_AVX == "1" || task.env.NSS_DISABLE_AVX2 == "1")) {
     return false;
   }
 
+  // Don't run ARM specific hardware tests on non-ARM.
+  // TODO: our server that runs task cluster doesn't support Intel SHA extensions.
+  if (task.group == "Cipher" && task.platform != "aarch64" && task.env &&
+      (task.env.NSS_DISABLE_HW_SHA1 == "1" || task.env.NSS_DISABLE_HW_SHA2 == "1")) {
+    return false;
+  }
+
   // Don't run DBM builds on aarch64.
   if (task.group == "DBM" && task.platform == "aarch64") {
     return false;
@@ -1003,9 +1010,16 @@ function scheduleTests(task_build, task_cert, test_base) {
     name: "Cipher tests", symbol: "Default", tests: "cipher", group: "Cipher"
   }));
   queue.scheduleTask(merge(cert_base_long, {
-    name: "Cipher tests", symbol: "NoAESNI", tests: "cipher",
+    name: "Cipher tests", symbol: "NoAES", tests: "cipher",
     env: {NSS_DISABLE_HW_AES: "1"}, group: "Cipher"
   }));
+  queue.scheduleTask(merge(cert_base_long, {
+    name: "Cipher tests", symbol: "NoSHA", tests: "cipher",
+    env: {
+      NSS_DISABLE_HW_SHA1: "1",
+      NSS_DISABLE_HW_SHA2: "1"
+    }, group: "Cipher"
+  }));
   queue.scheduleTask(merge(cert_base_long, {
     name: "Cipher tests", symbol: "NoPCLMUL", tests: "cipher",
     env: {NSS_DISABLE_PCLMUL: "1"}, group: "Cipher"
diff --git a/mozilla/security/nss/automation/taskcluster/scripts/build_gyp.sh b/mozilla/security/nss/automation/taskcluster/scripts/build_gyp.sh
index e19a6362fc9..2cb0deb0165 100755
--- a/mozilla/security/nss/automation/taskcluster/scripts/build_gyp.sh
+++ b/mozilla/security/nss/automation/taskcluster/scripts/build_gyp.sh
@@ -12,7 +12,7 @@ if [[ -f nss/nspr.patch && "$ALLOW_NSPR_PATCH" == "1" ]]; then
 fi
 
 # Build.
-nss/build.sh -g -v --enable-libpkix "$@"
+nss/build.sh -g -v --enable-libpkix -Denable_draft_hpke=1 "$@"
 
 # Package.
 if [[ $(uname) = "Darwin" ]]; then
diff --git a/mozilla/security/nss/automation/taskcluster/windows/build_gyp.sh b/mozilla/security/nss/automation/taskcluster/windows/build_gyp.sh
index 1621c257103..d7072ebbf29 100644
--- a/mozilla/security/nss/automation/taskcluster/windows/build_gyp.sh
+++ b/mozilla/security/nss/automation/taskcluster/windows/build_gyp.sh
@@ -38,7 +38,7 @@ if [[ -f nss/nspr.patch && "$ALLOW_NSPR_PATCH" == "1" ]]; then
 fi
 
 # Build with gyp.
-./nss/build.sh -g -v --enable-libpkix "$@"
+./nss/build.sh -g -v --enable-libpkix -Denable_draft_hpke=1 "$@"
 
 # Package.
 7z a public/build/dist.7z dist
diff --git a/mozilla/security/nss/coreconf/config.gypi b/mozilla/security/nss/coreconf/config.gypi
index 62d3cc71eca..a18a47dbc6f 100644
--- a/mozilla/security/nss/coreconf/config.gypi
+++ b/mozilla/security/nss/coreconf/config.gypi
@@ -129,6 +129,7 @@
     'mozpkix_only%': 0,
     'coverage%': 0,
     'softfp_cflags%': '',
+    'enable_draft_hpke%': 0,
   },
   'target_defaults': {
     # Settings specific to targets should go here.
@@ -565,6 +566,11 @@
               'NSS_DISABLE_DBM',
             ],
           }],
+          [ 'enable_draft_hpke==1', {
+            'defines': [
+              'NSS_ENABLE_DRAFT_HPKE',
+            ],
+          }],
           [ 'disable_libpkix==1', {
             'defines': [
               'NSS_DISABLE_LIBPKIX',
diff --git a/mozilla/security/nss/coreconf/config.mk b/mozilla/security/nss/coreconf/config.mk
index 39f755d01a9..5e04175649c 100644
--- a/mozilla/security/nss/coreconf/config.mk
+++ b/mozilla/security/nss/coreconf/config.mk
@@ -164,6 +164,10 @@ ifdef NSS_PKIX_NO_LDAP
 DEFINES += -DNSS_PKIX_NO_LDAP
 endif
 
+ifdef NSS_ENABLE_DRAFT_HPKE
+DEFINES += -DNSS_ENABLE_DRAFT_HPKE
+endif
+
 # FIPS support requires startup tests to be executed at load time of shared modules.
 # For performance reasons, these tests are disabled by default.
 # When compiling binaries that must support FIPS mode,
diff --git a/mozilla/security/nss/cpputil/nss_scoped_ptrs.h b/mozilla/security/nss/cpputil/nss_scoped_ptrs.h
index 501f9dfe878..2c57986b145 100644
--- a/mozilla/security/nss/cpputil/nss_scoped_ptrs.h
+++ b/mozilla/security/nss/cpputil/nss_scoped_ptrs.h
@@ -11,6 +11,7 @@
 #include "cert.h"
 #include "keyhi.h"
 #include "p12.h"
+#include "pk11hpke.h"
 #include "pk11pqg.h"
 #include "pk11pub.h"
 #include "pkcs11uri.h"
@@ -27,6 +28,9 @@ struct ScopedDelete {
   void operator()(CERTSubjectPublicKeyInfo* spki) {
     SECKEY_DestroySubjectPublicKeyInfo(spki);
   }
+  void operator()(HpkeContext* context) {
+    PK11_HPKE_DestroyContext(context, true);
+  }
   void operator()(PK11Context* context) { PK11_DestroyContext(context, true); }
   void operator()(PK11GenericObject* obj) { PK11_DestroyGenericObject(obj); }
   void operator()(PK11SlotInfo* slot) { PK11_FreeSlot(slot); }
@@ -70,6 +74,7 @@ SCOPED(CERTCertificateList);
 SCOPED(CERTDistNames);
 SCOPED(CERTName);
 SCOPED(CERTSubjectPublicKeyInfo);
+SCOPED(HpkeContext);
 SCOPED(PK11Context);
 SCOPED(PK11GenericObject);
 SCOPED(PK11SlotInfo);
diff --git a/mozilla/security/nss/gtests/common/testvectors/hpke-vectors.h b/mozilla/security/nss/gtests/common/testvectors/hpke-vectors.h
new file mode 100644
index 00000000000..dd7b417b8ac
--- /dev/null
+++ b/mozilla/security/nss/gtests/common/testvectors/hpke-vectors.h
@@ -0,0 +1,233 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef hpke_vectors_h__
+#define hpke_vectors_h__
+
+#include "pk11hpke.h"
+#include <vector>
+
+typedef struct hpke_encrypt_vector_str {
+  std::string pt;
+  std::string aad;
+  std::string ct;
+} hpke_encrypt_vector;
+
+typedef struct hpke_export_vector_str {
+  std::string ctxt;
+  size_t len;
+  std::string exported;
+} hpke_export_vector;
+
+/* Note: The following test vec values are implicitly checked via:
+ * shared_secret: secret derivation
+ * key_sched_context: key/nonce derivations
+ * secret: key/nonce derivations
+ * exporter_secret: export vectors */
+typedef struct hpke_vector_str {
+  uint32_t test_id;
+  HpkeModeId mode;
+  HpkeKemId kem_id;
+  HpkeKdfId kdf_id;
+  HpkeAeadId aead_id;
+  std::string info;
+  std::string pkcs8_e;
+  std::string pkcs8_r;
+  std::string psk;
+  std::string psk_id;
+  std::string enc;
+  std::string key;
+  std::string nonce;
+  std::vector<hpke_encrypt_vector> encrypt_vecs;
+  std::vector<hpke_export_vector> export_vecs;
+} hpke_vector;
+
+const hpke_vector kHpkeTestVectors[] = {
+    // A.1. DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-128-GCM, Base mode
+    {0,
+     static_cast<HpkeModeId>(0),
+     static_cast<HpkeKemId>(32),
+     static_cast<HpkeKdfId>(1),
+     static_cast<HpkeAeadId>(1),
+     "4f6465206f6e2061204772656369616e2055726e",
+     "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a"
+     "02010104208c490e5b0c7dbe0c6d2192484d2b7a0423b3b4544f2481095a9"
+     "9dbf238fb350fa1230321008a07563949fac6232936ed6f36c4fa735930ecd"
+     "eaef6734e314aeac35a56fd0a",
+     "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a"
+     "02010104205a8aa0d2476b28521588e0c704b14db82cdd4970d340d293a957"
+     "6deaee9ec1c7a1230321008756e2580c07c1d2ffcb662f5fadc6d6ff13da85"
+     "abd7adfecf984aaa102c1269",
+     "",
+     "",
+     "8a07563949fac6232936ed6f36c4fa735930ecdeaef6734e314aeac35a56fd0a",
+     "550ee0b7ec1ea2532f2e2bac87040a4c",
+     "2b855847756795a57229559a",
+     {// Encryptions
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d30",
+       "971ba65db526758ea30ae748cd769bc8d90579b62a037816057f24ce4274"
+       "16bd47c05ed1c2446ac8e19ec9ae79"},
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d31",
+       "f18f1ec397667ca069b9a6ee0bebf0890cd5caa34bb9875b3600ca0142cb"
+       "a774dd35f2aafd79a02a08ca5f2806"},
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d32",
+       "51a8dea350fe6e753f743ec17c956de4cbdfa35f3018fc6a12752c51d137"
+       "2c5093959f18c7253da9c953c6cfbe"}},
+     {// Exports
+      {"436f6e746578742d30", 32,
+       "0df04ac640d34a56561419bab20a68e6b7331070208004f89c7b973f4c47"
+       "2e92"},
+      {"436f6e746578742d31", 32,
+       "723c2c8f80e6b827e72bd8e80973a801a05514afe3d4bc46e82e505dceb9"
+       "53aa"},
+      {"436f6e746578742d32", 32,
+       "38010c7d5d81093a11b55e2403a258e9a195bcf066817b332dd996b0a9bc"
+       "bc9a"},
+      {"436f6e746578742d33", 32,
+       "ebf6ab4c3186131de9b2c3c0bc3e2ad21dfcbc4efaf050cd0473f5b1535a"
+       "8b6d"},
+      {"436f6e746578742d34", 32,
+       "c4823eeb3efd2d5216b2d3b16e542bf57470dc9b9ea9af6bce85b151a358"
+       "9d90"}}},
+
+    // A.1. DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-128-GCM, PSK mode
+    {1,
+     static_cast<HpkeModeId>(1),
+     static_cast<HpkeKemId>(32),
+     static_cast<HpkeKdfId>(1),
+     static_cast<HpkeAeadId>(1),
+     "4f6465206f6e2061204772656369616e2055726e",
+     "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020"
+     "1010420e7d2b539792a48a24451303ccd0cfe77176b6cb06823c439edfd217458"
+     "a1398aa12303210008d39d3e7f9b586341b6004dafba9679d2bd9340066edb247"
+     "e3e919013efcd0f",
+     "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020"
+     "10104204b41ef269169090551fcea177ecdf622bca86d82298e21cd93119b804c"
+     "cc5eaba123032100a5c85773bed3a831e7096f7df4ff5d1d8bac48fc97bfac366"
+     "141efab91892a3a",
+     "5db3b80a81cb63ca59470c83414ef70a",
+     "456e6e796e20447572696e206172616e204d6f726961",
+     "08d39d3e7f9b586341b6004dafba9679d2bd9340066edb247e3e919013efcd0f",
+     "811e9b2d7a10f4f9d58786bf8a534ca6",
+     "b79b0c5a8c3808e238b10411",
+     {// Encryptions
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d30",
+       "fb68f911b4e4033d1547f646ea30c9cee987fb4b4a8c30918e5de6e96de32fc"
+       "63466f2fc05e09aeff552489741"},
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d31",
+       "85e7472fbb7e2341af35fb2a0795df9a85caa99a8f584056b11d452bc160470"
+       "672e297f9892ce2c5020e794ae1"},
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d32",
+       "74229b7491102bcf94cf7633888bc48baa4e5a73cc544bfad4ff61585506fac"
+       "b44b359ade03c0b2b35c6430e4c"}},
+     {// Exports
+      {"436f6e746578742d30", 32,
+       "bd292b132fae00243851451c3f3a87e9e11c3293c14d61b114b7e12e07245ffd"},
+      {"436f6e746578742d31", 32,
+       "695de26bc9336caee01cb04826f6e224f4d2108066ab17fc18f0c993dce05f24"},
+      {"436f6e746578742d32", 32,
+       "c53f26ef1bf4f5fd5469d807c418a0e103d035c76ccdbc6afb5bc42b24968f6c"},
+      {"436f6e746578742d33", 32,
+       "8cea4a595dfe3de84644ca8ea7ea9401a345f0db29bb4beebc2c471afc602ec4"},
+      {"436f6e746578742d34", 32,
+       "e6313f12f6c2054c69018f273211c54fcf2439d90173392eaa34b4caac929068"}}},
+
+    // A.2. DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305, Base mode
+    {2,
+     static_cast<HpkeModeId>(0),
+     static_cast<HpkeKemId>(32),
+     static_cast<HpkeKdfId>(1),
+     static_cast<HpkeAeadId>(3),
+     "4f6465206f6e2061204772656369616e2055726e",
+     "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020"
+     "10104205006a9a0f0138b9b5d577ed4a67c4f795aee8fc146ac63d7a4167765be"
+     "3ad7dca123032100716281787b035b2fee90455d951fa70b3db6cc92f13bedfd7"
+     "58c3487994b7020",
+     "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020"
+     "101042062139576dcbf9878ccd56262d1b28dbea897821c03370d81971513cc74"
+     "aea3ffa1230321001ae26f65041b36ad69eb392c198bfd33df1c6ff17a910cb3e"
+     "49db7506b6a4e7f",
+     "",
+     "",
+     "716281787b035b2fee90455d951fa70b3db6cc92f13bedfd758c3487994b7020",
+     "1d5e71e2885ddadbcc479798cc65ea74d308f2a9e99c0cc7fe480adce66b5722",
+     "8354a7fcfef97d4bbef6d24e",
+     {// Encryptions
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d30",
+       "fa4632a400962c98143e58450e75d879365359afca81a5f5b5997c6555647ec"
+       "302045a80c57d3e2c2abe7e1ced"},
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d31",
+       "8313fcbf760714f5a93b6864820e48dcec3ddd476ad4408ff1c1a1f7bfb8cb8"
+       "699fada4a9e59bf8086eb1c0635"},
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d32",
+       "020f2856d95b85e1def9549bf327c484d327616f1e213045f117be4c287571a"
+       "b983958f74766cbc6f8197c8d8d"}},
+     {// Exports
+      {"436f6e746578742d30", 32,
+       "22bbe971392c685b55e13544cdaf976f36b89dc1dbe1296c2884971a5aa9e331"},
+      {"436f6e746578742d31", 32,
+       "5c0fa72053a2622d8999b726446db9ef743e725e2cb040afac2d83eae0d41981"},
+      {"436f6e746578742d32", 32,
+       "72b0f9999fd37ac2b948a07dadd01132587501a5a9460d596c1f7383299a2442"},
+      {"436f6e746578742d33", 32,
+       "73d2308ed5bdd63aacd236effa0db2d3a30742b6293a924d95a372e76d90486b"},
+      {"436f6e746578742d34", 32,
+       "d4f8878dbc471935e86cdee08746e53837bbb4b6013003bebb0bc1cc3e074085"}}},
+
+    // A.2. DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305, PSK mode
+    {3,
+     static_cast<HpkeModeId>(1),
+     static_cast<HpkeKemId>(32),
+     static_cast<HpkeKdfId>(1),
+     static_cast<HpkeAeadId>(3),
+     "4f6465206f6e2061204772656369616e2055726e",
+     "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020"
+     "10104204bfdb62b95ae2a1f29f20ea49e24aa2673e0d240c6e967f668f55ed5de"
+     "e996dca123032100f4639297e3305b03d34dd5d86522ddc6ba11a608a0003670a"
+     "30734823cdd3763",
+     "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a020"
+     "1010420a6ab4e1bb782d580d837843089d65ebe271a0ee9b5a951777cecf1293c"
+     "58c150a123032100c49b46ed73ecb7d3a6a3e44f54b8f00f9ab872b57dd79ded6"
+     "6d7231a14c64144",
+     "5db3b80a81cb63ca59470c83414ef70a",
+     "456e6e796e20447572696e206172616e204d6f726961",
+     "f4639297e3305b03d34dd5d86522ddc6ba11a608a0003670a30734823cdd3763",
+     "396c06a52b39d0930594aa2c6944561cc1741f638557a12bef1c1cad349157c9",
+     "baa4ecf96b5d6d536d0d7210",
+     {// Encryptions
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d30",
+       "f97ca72675b8199e8ffec65b4c200d901110b177b246f241b6f9716fb60b35b"
+       "32a6d452675534b591e8141468a"},
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d31",
+       "57796e2b9dd0ddf807f1a7cb5884dfc50e61468c4fd69fa03963731e51674ca"
+       "88fee94eeac3290734e1627ded6"},
+      {"4265617574792069732074727574682c20747275746820626561757479",
+       "436f756e742d32",
+       "b514150af1057151687d0036a9b4a3ad50fb186253f839d8433622baa85719e"
+       "d5d2532017a0ce7b9ca0007f276"}},
+     {// Exports
+      {"436f6e746578742d30", 32,
+       "735400cd9b9193daffe840f412074728ade6b1978e9ae27957aacd588dbd7c9e"},
+      {"436f6e746578742d31", 32,
+       "cf4e351e1943d171ff2d88726f18160086ecbec52a8151dba8cf5ba0737a6097"},
+      {"436f6e746578742d32", 32,
+       "8e23b44d4f23dd906d1c100580a670d171132c9786212c4ca2876a1541a84fae"},
+      {"436f6e746578742d33", 32,
+       "56252a940ece53d4013eb619b444ee1d019a08eec427ded2b6dbf24be624a4a0"},
+      {"436f6e746578742d34", 32,
+       "fc6cdca9ce8ab062401478ffd16ee1c07e2b15d7c781d4227f07c6043d937fad"}}}};
+
+#endif  // hpke_vectors_h__
diff --git a/mozilla/security/nss/gtests/pk11_gtest/manifest.mn b/mozilla/security/nss/gtests/pk11_gtest/manifest.mn
index 12a4c2c1d81..9b127159f9a 100644
--- a/mozilla/security/nss/gtests/pk11_gtest/manifest.mn
+++ b/mozilla/security/nss/gtests/pk11_gtest/manifest.mn
@@ -21,6 +21,7 @@ CPPSRCS = \
       pk11_encrypt_derive_unittest.cc \
       pk11_export_unittest.cc \
       pk11_find_certs_unittest.cc \
+      pk11_hpke_unittest.cc \
       pk11_hkdf_unittest.cc \
       pk11_import_unittest.cc \
       pk11_kbkdf.cc \
diff --git a/mozilla/security/nss/gtests/pk11_gtest/pk11_gtest.gyp b/mozilla/security/nss/gtests/pk11_gtest/pk11_gtest.gyp
index cdad1a96360..17197f9e095 100644
--- a/mozilla/security/nss/gtests/pk11_gtest/pk11_gtest.gyp
+++ b/mozilla/security/nss/gtests/pk11_gtest/pk11_gtest.gyp
@@ -27,6 +27,7 @@
         'pk11_encrypt_derive_unittest.cc',
         'pk11_find_certs_unittest.cc',
         'pk11_hkdf_unittest.cc',
+        'pk11_hpke_unittest.cc',
         'pk11_import_unittest.cc',
         'pk11_kbkdf.cc',
         'pk11_keygen.cc',
diff --git a/mozilla/security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc b/mozilla/security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc
new file mode 100644
index 00000000000..1858e7f1099
--- /dev/null
+++ b/mozilla/security/nss/gtests/pk11_gtest/pk11_hpke_unittest.cc
@@ -0,0 +1,547 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <memory>
+#include "blapi.h"
+#include "gtest/gtest.h"
+#include "nss.h"
+#include "nss_scoped_ptrs.h"
+#include "pk11hpke.h"
+#include "pk11pub.h"
+#include "secerr.h"
+#include "sechash.h"
+#include "testvectors/hpke-vectors.h"
+#include "util.h"
+
+namespace nss_test {
+
+/* See note in pk11pub.h. */
+#ifdef NSS_ENABLE_DRAFT_HPKE
+#include "cpputil.h"
+
+class Pkcs11HpkeTest : public ::testing::TestWithParam<hpke_vector> {
+ protected:
+  void ReadVector(const hpke_vector &vec) {
+    ScopedPK11SymKey vec_psk;
+    if (!vec.psk.empty()) {
+      ASSERT_FALSE(vec.psk_id.empty());
+      vec_psk_id = hex_string_to_bytes(vec.psk_id);
+
+      std::vector<uint8_t> psk_bytes = hex_string_to_bytes(vec.psk);
+      SECItem psk_item = {siBuffer, toUcharPtr(psk_bytes.data()),
+                          static_cast<unsigned int>(psk_bytes.size())};
+      ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+      ASSERT_TRUE(slot);
+      PK11SymKey *psk_key =
+          PK11_ImportSymKey(slot.get(), CKM_HKDF_KEY_GEN, PK11_OriginUnwrap,
+                            CKA_WRAP, &psk_item, nullptr);
+      ASSERT_NE(nullptr, psk_key);
+      vec_psk_key.reset(psk_key);
+    }
+
+    vec_pkcs8_r = hex_string_to_bytes(vec.pkcs8_r);
+    vec_pkcs8_e = hex_string_to_bytes(vec.pkcs8_e);
+    vec_key = hex_string_to_bytes(vec.key);
+    vec_nonce = hex_string_to_bytes(vec.nonce);
+    vec_enc = hex_string_to_bytes(vec.enc);
+    vec_info = hex_string_to_bytes(vec.info);
+    vec_encryptions = vec.encrypt_vecs;
+    vec_exports = vec.export_vecs;
+  }
+
+  void CheckEquality(const std::vector<uint8_t> &expected, SECItem *actual) {
+    if (!actual) {
+      EXPECT_TRUE(expected.empty());
+      return;
+    }
+    std::vector<uint8_t> vact(actual->data, actual->data + actual->len);
+    EXPECT_EQ(expected, vact);
+  }
+
+  void CheckEquality(SECItem *expected, SECItem *actual) {
+    EXPECT_EQ(!!expected, !!actual);
+    if (expected && actual) {
+      EXPECT_EQ(expected->len, actual->len);
+      if (expected->len == actual->len) {
+        EXPECT_EQ(0, memcmp(expected->data, actual->data, actual->len));
+      }
+    }
+  }
+
+  void CheckEquality(const std::vector<uint8_t> &expected, PK11SymKey *actual) {
+    if (!actual) {
+      EXPECT_TRUE(expected.empty());
+      return;
+    }
+    SECStatus rv = PK11_ExtractKeyValue(actual);
+    EXPECT_EQ(SECSuccess, rv);
+    if (rv != SECSuccess) {
+      return;
+    }
+    SECItem *rawkey = PK11_GetKeyData(actual);
+    CheckEquality(expected, rawkey);
+  }
+
+  void CheckEquality(PK11SymKey *expected, PK11SymKey *actual) {
+    if (!actual || !expected) {
+      EXPECT_EQ(!!expected, !!actual);
+      return;
+    }
+    SECStatus rv = PK11_ExtractKeyValue(expected);
+    EXPECT_EQ(SECSuccess, rv);
+    if (rv != SECSuccess) {
+      return;
+    }
+    SECItem *raw = PK11_GetKeyData(expected);
+    ASSERT_NE(nullptr, raw);
+    ASSERT_NE(nullptr, raw->data);
+    std::vector<uint8_t> expected_vec(raw->data, raw->data + raw->len);
+    CheckEquality(expected_vec, actual);
+  }
+
+  void SetupS(const ScopedHpkeContext &cx, const ScopedSECKEYPublicKey &pkE,
+              const ScopedSECKEYPrivateKey &skE,
+              const ScopedSECKEYPublicKey &pkR,
+              const std::vector<uint8_t> &info) {
+    SECItem info_item = {siBuffer, toUcharPtr(vec_info.data()),
+                         static_cast<unsigned int>(vec_info.size())};
+    SECStatus rv =
+        PK11_HPKE_SetupS(cx.get(), pkE.get(), skE.get(), pkR.get(), &info_item);
+    EXPECT_EQ(SECSuccess, rv);
+  }
+
+  void SetupR(const ScopedHpkeContext &cx, const ScopedSECKEYPublicKey &pkR,
+              const ScopedSECKEYPrivateKey &skR,
+              const std::vector<uint8_t> &enc,
+              const std::vector<uint8_t> &info) {
+    SECItem enc_item = {siBuffer, toUcharPtr(enc.data()),
+                        static_cast<unsigned int>(enc.size())};
+    SECItem info_item = {siBuffer, toUcharPtr(vec_info.data()),
+                         static_cast<unsigned int>(vec_info.size())};
+    SECStatus rv =
+        PK11_HPKE_SetupR(cx.get(), pkR.get(), skR.get(), &enc_item, &info_item);
+    EXPECT_EQ(SECSuccess, rv);
+  }
+
+  void Seal(const ScopedHpkeContext &cx, std::vector<uint8_t> &aad_vec,
+            std::vector<uint8_t> &pt_vec, SECItem **out_ct) {
+    SECItem aad_item = {siBuffer, toUcharPtr(aad_vec.data()),
+                        static_cast<unsigned int>(aad_vec.size())};
+    SECItem pt_item = {siBuffer, toUcharPtr(pt_vec.data()),
+                       static_cast<unsigned int>(pt_vec.size())};
+
+    SECStatus rv = PK11_HPKE_Seal(cx.get(), &aad_item, &pt_item, out_ct);
+    EXPECT_EQ(SECSuccess, rv);
+  }
+
+  void Open(const ScopedHpkeContext &cx, std::vector<uint8_t> &aad_vec,
+            std::vector<uint8_t> &ct_vec, SECItem **out_pt) {
+    SECItem aad_item = {siBuffer, toUcharPtr(aad_vec.data()),
+                        static_cast<unsigned int>(aad_vec.size())};
+    SECItem ct_item = {siBuffer, toUcharPtr(ct_vec.data()),
+                       static_cast<unsigned int>(ct_vec.size())};
+    SECStatus rv = PK11_HPKE_Open(cx.get(), &aad_item, &ct_item, out_pt);
+    EXPECT_EQ(SECSuccess, rv);
+  }
+
+  void TestExports(const ScopedHpkeContext &sender,
+                   const ScopedHpkeContext &receiver) {
+    SECStatus rv;
+
+    for (auto &vec : vec_exports) {
+      std::vector<uint8_t> context = hex_string_to_bytes(vec.ctxt);
+      std::vector<uint8_t> expected = hex_string_to_bytes(vec.exported);
+      SECItem context_item = {siBuffer, toUcharPtr(context.data()),
+                              static_cast<unsigned int>(context.size())};
+      PK11SymKey *actual_r = nullptr;
+      PK11SymKey *actual_s = nullptr;
+      rv = PK11_HPKE_ExportSecret(sender.get(), &context_item, vec.len,
+                                  &actual_s);
+      ASSERT_EQ(SECSuccess, rv);
+      rv = PK11_HPKE_ExportSecret(receiver.get(), &context_item, vec.len,
+                                  &actual_r);
+      ASSERT_EQ(SECSuccess, rv);
+      ScopedPK11SymKey scoped_act_s(actual_s);
+      ScopedPK11SymKey scoped_act_r(actual_r);
+      CheckEquality(expected, scoped_act_s.get());
+      CheckEquality(expected, scoped_act_r.get());
+    }
+  }
+
+  void TestEncryptions(const ScopedHpkeContext &sender,
+                       const ScopedHpkeContext &receiver) {
+    for (auto &enc_vec : vec_encryptions) {
+      std::vector<uint8_t> msg = hex_string_to_bytes(enc_vec.pt);
+      std::vector<uint8_t> aad = hex_string_to_bytes(enc_vec.aad);
+      std::vector<uint8_t> expect_ct = hex_string_to_bytes(enc_vec.ct);
+      SECItem *act_ct = nullptr;
+      Seal(sender, aad, msg, &act_ct);
+      CheckEquality(expect_ct, act_ct);
+      ScopedSECItem scoped_ct(act_ct);
+
+      SECItem *act_pt = nullptr;
+      Open(receiver, aad, expect_ct, &act_pt);
+      CheckEquality(msg, act_pt);
+      ScopedSECItem scoped_pt(act_pt);
+    }
+  }
+
+  void ImportKeyPairs(const ScopedHpkeContext &sender,
+                      const ScopedHpkeContext &receiver) {
+    ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+    if (!slot) {
+      ADD_FAILURE() << "No slot";
+      return;
+    }
+
+    SECItem pkcs8_e_item = {siBuffer, toUcharPtr(vec_pkcs8_e.data()),
+                            static_cast<unsigned int>(vec_pkcs8_e.size())};
+    SECKEYPrivateKey *sk_e = nullptr;
+    SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
+        slot.get(), &pkcs8_e_item, nullptr, nullptr, false, false, KU_ALL,
+        &sk_e, nullptr);
+    EXPECT_EQ(SECSuccess, rv);
+    skE_derived.reset(sk_e);
+    SECKEYPublicKey *pk_e = SECKEY_ConvertToPublicKey(skE_derived.get());
+    ASSERT_NE(nullptr, pk_e);
+    pkE_derived.reset(pk_e);
+
+    SECItem pkcs8_r_item = {siBuffer, toUcharPtr(vec_pkcs8_r.data()),
+                            static_cast<unsigned int>(vec_pkcs8_r.size())};
+    SECKEYPrivateKey *sk_r = nullptr;
+    rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
+        slot.get(), &pkcs8_r_item, nullptr, nullptr, false, false, KU_ALL,
+        &sk_r, nullptr);
+    EXPECT_EQ(SECSuccess, rv);
+    skR_derived.reset(sk_r);
+    SECKEYPublicKey *pk_r = SECKEY_ConvertToPublicKey(skR_derived.get());
+    ASSERT_NE(nullptr, pk_r);
+    pkR_derived.reset(pk_r);
+  }
+
+  void SetupSenderReceiver(const ScopedHpkeContext &sender,
+                           const ScopedHpkeContext &receiver) {
+    SetupS(sender, pkE_derived, skE_derived, pkR_derived, vec_info);
+    uint8_t buf[32];  // Curve25519 only, fixed size.
+    SECItem encap_item = {siBuffer, const_cast<uint8_t *>(buf), sizeof(buf)};
+    SECStatus rv = PK11_HPKE_Serialize(pkE_derived.get(), encap_item.data,
+                                       &encap_item.len, encap_item.len);
+    ASSERT_EQ(SECSuccess, rv);
+    CheckEquality(vec_enc, &encap_item);
+    SetupR(receiver, pkR_derived, skR_derived, vec_enc, vec_info);
+  }
+
+  bool GenerateKeyPair(ScopedSECKEYPublicKey &pub_key,
+                       ScopedSECKEYPrivateKey &priv_key) {
+    unsigned char param_buf[65];
+
+    ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+    if (!slot) {
+      ADD_FAILURE() << "Couldn't get slot";
+      return false;
+    }
+
+    SECItem ecdsa_params = {siBuffer, param_buf, sizeof(param_buf)};
+    SECOidData *oid_data = SECOID_FindOIDByTag(SEC_OID_CURVE25519);
+    if (!oid_data) {
+      ADD_FAILURE() << "Couldn't get oid_data";
+      return false;
+    }
+
+    ecdsa_params.data[0] = SEC_ASN1_OBJECT_ID;
+    ecdsa_params.data[1] = oid_data->oid.len;
+    memcpy(ecdsa_params.data + 2, oid_data->oid.data, oid_data->oid.len);
+    ecdsa_params.len = oid_data->oid.len + 2;
+    SECKEYPublicKey *pub_tmp;
+    SECKEYPrivateKey *priv_tmp;
+    priv_tmp =
+        PK11_GenerateKeyPair(slot.get(), CKM_EC_KEY_PAIR_GEN, &ecdsa_params,
+                             &pub_tmp, PR_FALSE, PR_TRUE, nullptr);
+    if (!pub_tmp || !priv_tmp) {
+      ADD_FAILURE() << "PK11_GenerateKeyPair failed";
+      return false;
+    }
+
+    pub_key.reset(pub_tmp);
+    priv_key.reset(priv_tmp);
+    return true;
+  }
+
+  void RunTestVector(const hpke_vector &vec) {
+    ReadVector(vec);
+    SECItem psk_id_item = {siBuffer, toUcharPtr(vec_psk_id.data()),
+                           static_cast<unsigned int>(vec_psk_id.size())};
+    PK11SymKey *psk = vec_psk_key ? vec_psk_key.get() : nullptr;
+    SECItem *psk_id = psk ? &psk_id_item : nullptr;
+
+    ScopedHpkeContext sender(
+        PK11_HPKE_NewContext(vec.kem_id, vec.kdf_id, vec.aead_id, psk, psk_id));
+    ScopedHpkeContext receiver(
+        PK11_HPKE_NewContext(vec.kem_id, vec.kdf_id, vec.aead_id, psk, psk_id));
+    ASSERT_TRUE(sender);
+    ASSERT_TRUE(receiver);
+
+    ImportKeyPairs(sender, receiver);
+    SetupSenderReceiver(sender, receiver);
+    TestEncryptions(sender, receiver);
+    TestExports(sender, receiver);
+  }
+
+ private:
+  ScopedPK11SymKey vec_psk_key;
+  std::vector<uint8_t> vec_psk_id;
+  std::vector<uint8_t> vec_pkcs8_e;
+  std::vector<uint8_t> vec_pkcs8_r;
+  std::vector<uint8_t> vec_enc;
+  std::vector<uint8_t> vec_info;
+  std::vector<uint8_t> vec_key;
+  std::vector<uint8_t> vec_nonce;
+  std::vector<hpke_encrypt_vector> vec_encryptions;
+  std::vector<hpke_export_vector> vec_exports;
+  ScopedSECKEYPublicKey pkE_derived;
+  ScopedSECKEYPublicKey pkR_derived;
+  ScopedSECKEYPrivateKey skE_derived;
+  ScopedSECKEYPrivateKey skR_derived;
+};
+
+TEST_P(Pkcs11HpkeTest, TestVectors) { RunTestVector(GetParam()); }
+
+INSTANTIATE_TEST_CASE_P(Pkcs11HpkeTests, Pkcs11HpkeTest,
+                        ::testing::ValuesIn(kHpkeTestVectors));
+
+TEST_F(Pkcs11HpkeTest, BadEncapsulatedPubKey) {
+  ScopedHpkeContext sender(
+      PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
+                           HpkeAeadAes128Gcm, nullptr, nullptr));
+  ScopedHpkeContext receiver(
+      PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
+                           HpkeAeadAes128Gcm, nullptr, nullptr));
+
+  SECItem empty = {siBuffer, nullptr, 0};
+  uint8_t buf[100];
+  SECItem short_encap = {siBuffer, buf, 1};
+  SECItem long_encap = {siBuffer, buf, sizeof(buf)};
+
+  SECKEYPublicKey *tmp_pub_key;
+  ScopedSECKEYPublicKey pub_key;
+  ScopedSECKEYPrivateKey priv_key;
+  ASSERT_TRUE(GenerateKeyPair(pub_key, priv_key));
+
+  // Decapsulating an empty buffer should fail.
+  SECStatus rv =
+      PK11_HPKE_Deserialize(sender.get(), empty.data, empty.len, &tmp_pub_key);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  // Decapsulating anything else will succeed, but the setup will fail.
+  rv = PK11_HPKE_Deserialize(sender.get(), short_encap.data, short_encap.len,
+                             &tmp_pub_key);
+  ScopedSECKEYPublicKey bad_pub_key(tmp_pub_key);
+  EXPECT_EQ(SECSuccess, rv);
+
+  rv = PK11_HPKE_SetupS(receiver.get(), pub_key.get(), priv_key.get(),
+                        bad_pub_key.get(), &empty);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(SEC_ERROR_INVALID_KEY, PORT_GetError());
+
+  // Test the same for a receiver.
+  rv = PK11_HPKE_SetupR(sender.get(), pub_key.get(), priv_key.get(), &empty,
+                        &empty);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  rv = PK11_HPKE_SetupR(sender.get(), pub_key.get(), priv_key.get(),
+                        &short_encap, &empty);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(SEC_ERROR_INVALID_KEY, PORT_GetError());
+
+  // Encapsulated key too long
+  rv = PK11_HPKE_Deserialize(sender.get(), long_encap.data, long_encap.len,
+                             &tmp_pub_key);
+  bad_pub_key.reset(tmp_pub_key);
+  EXPECT_EQ(SECSuccess, rv);
+
+  rv = PK11_HPKE_SetupS(receiver.get(), pub_key.get(), priv_key.get(),
+                        bad_pub_key.get(), &empty);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  rv = PK11_HPKE_SetupR(sender.get(), pub_key.get(), priv_key.get(),
+                        &long_encap, &empty);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+}
+
+// Vectors used fixed keypairs on each end. Make sure the
+// ephemeral (particularly sender) path works.
+TEST_F(Pkcs11HpkeTest, EphemeralKeys) {
+  unsigned char info[] = {"info"};
+  unsigned char msg[] = {"secret"};
+  unsigned char aad[] = {"aad"};
+  SECItem info_item = {siBuffer, info, sizeof(info)};
+  SECItem msg_item = {siBuffer, msg, sizeof(msg)};
+  SECItem aad_item = {siBuffer, aad, sizeof(aad)};
+
+  ScopedHpkeContext sender(
+      PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
+                           HpkeAeadAes128Gcm, nullptr, nullptr));
+  ScopedHpkeContext receiver(
+      PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
+                           HpkeAeadAes128Gcm, nullptr, nullptr));
+  ASSERT_TRUE(sender);
+  ASSERT_TRUE(receiver);
+
+  ScopedSECKEYPublicKey pub_key_r;
+  ScopedSECKEYPrivateKey priv_key_r;
+  ASSERT_TRUE(GenerateKeyPair(pub_key_r, priv_key_r));
+
+  SECStatus rv = PK11_HPKE_SetupS(sender.get(), nullptr, nullptr,
+                                  pub_key_r.get(), &info_item);
+  EXPECT_EQ(SECSuccess, rv);
+
+  const SECItem *enc = PK11_HPKE_GetEncapPubKey(sender.get());
+  EXPECT_NE(nullptr, enc);
+  rv = PK11_HPKE_SetupR(receiver.get(), pub_key_r.get(), priv_key_r.get(),
+                        const_cast<SECItem *>(enc), &info_item);
+  EXPECT_EQ(SECSuccess, rv);
+
+  SECItem *tmp_sealed = nullptr;
+  rv = PK11_HPKE_Seal(sender.get(), &aad_item, &msg_item, &tmp_sealed);
+  EXPECT_EQ(SECSuccess, rv);
+  ScopedSECItem sealed(tmp_sealed);
+
+  SECItem *tmp_unsealed = nullptr;
+  rv = PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), &tmp_unsealed);
+  EXPECT_EQ(SECSuccess, rv);
+  CheckEquality(&msg_item, tmp_unsealed);
+  ScopedSECItem unsealed(tmp_unsealed);
+
+  // Once more
+  tmp_sealed = nullptr;
+  rv = PK11_HPKE_Seal(sender.get(), &aad_item, &msg_item, &tmp_sealed);
+  EXPECT_EQ(SECSuccess, rv);
+  ASSERT_NE(nullptr, sealed);
+  sealed.reset(tmp_sealed);
+  tmp_unsealed = nullptr;
+  rv = PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), &tmp_unsealed);
+  EXPECT_EQ(SECSuccess, rv);
+  CheckEquality(&msg_item, tmp_unsealed);
+  unsealed.reset(tmp_unsealed);
+
+  // Seal for negative tests
+  tmp_sealed = nullptr;
+  tmp_unsealed = nullptr;
+  rv = PK11_HPKE_Seal(sender.get(), &aad_item, &msg_item, &tmp_sealed);
+  EXPECT_EQ(SECSuccess, rv);
+  ASSERT_NE(nullptr, sealed);
+  sealed.reset(tmp_sealed);
+
+  // Drop AAD
+  rv = PK11_HPKE_Open(receiver.get(), nullptr, sealed.get(), &tmp_unsealed);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(nullptr, tmp_unsealed);
+
+  // Modify AAD
+  aad_item.data[0] ^= 0xff;
+  rv = PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), &tmp_unsealed);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(nullptr, tmp_unsealed);
+  aad_item.data[0] ^= 0xff;
+
+  // Modify ciphertext
+  sealed->data[0] ^= 0xff;
+  rv = PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), &tmp_unsealed);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(nullptr, tmp_unsealed);
+  sealed->data[0] ^= 0xff;
+
+  rv = PK11_HPKE_Open(receiver.get(), &aad_item, sealed.get(), &tmp_unsealed);
+  EXPECT_EQ(SECSuccess, rv);
+  EXPECT_NE(nullptr, tmp_unsealed);
+  unsealed.reset(tmp_unsealed);
+}
+
+TEST_F(Pkcs11HpkeTest, InvalidContextParams) {
+  HpkeContext *cx =
+      PK11_HPKE_NewContext(static_cast<HpkeKemId>(1), HpkeKdfHkdfSha256,
+                           HpkeAeadChaCha20Poly1305, nullptr, nullptr);
+  EXPECT_EQ(nullptr, cx);
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+
+  cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, static_cast<HpkeKdfId>(2),
+                            HpkeAeadChaCha20Poly1305, nullptr, nullptr);
+  EXPECT_EQ(nullptr, cx);
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+  cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
+                            static_cast<HpkeAeadId>(4), nullptr, nullptr);
+  EXPECT_EQ(nullptr, cx);
+  EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
+}
+
+TEST_F(Pkcs11HpkeTest, InvalidReceiverKeyType) {
+  ScopedHpkeContext sender(
+      PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
+                           HpkeAeadChaCha20Poly1305, nullptr, nullptr));
+  ASSERT_TRUE(!!sender);
+
+  ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+  if (!slot) {
+    ADD_FAILURE() << "No slot";
+    return;
+  }
+
+  // Give the client an RSA key
+  PK11RSAGenParams rsa_param;
+  rsa_param.keySizeInBits = 1024;
+  rsa_param.pe = 65537L;
+  SECKEYPublicKey *pub_tmp;
+  ScopedSECKEYPublicKey pub_key;
+  ScopedSECKEYPrivateKey priv_key(
+      PK11_GenerateKeyPair(slot.get(), CKM_RSA_PKCS_KEY_PAIR_GEN, &rsa_param,
+                           &pub_tmp, PR_FALSE, PR_FALSE, nullptr));
+  ASSERT_NE(nullptr, priv_key);
+  ASSERT_NE(nullptr, pub_tmp);
+  pub_key.reset(pub_tmp);
+
+  SECItem info_item = {siBuffer, nullptr, 0};
+  SECStatus rv = PK11_HPKE_SetupS(sender.get(), nullptr, nullptr, pub_key.get(),
+                                  &info_item);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(SEC_ERROR_BAD_KEY, PORT_GetError());
+
+  // Try with an unexpected curve
+  StackSECItem ecParams;
+  SECOidData *oidData = SECOID_FindOIDByTag(SEC_OID_ANSIX962_EC_PRIME256V1);
+  ASSERT_NE(oidData, nullptr);
+  if (!SECITEM_AllocItem(nullptr, &ecParams, (2 + oidData->oid.len))) {
+    FAIL() << "Couldn't allocate memory for OID.";
+  }
+  ecParams.data[0] = SEC_ASN1_OBJECT_ID;
+  ecParams.data[1] = oidData->oid.len;
+  memcpy(ecParams.data + 2, oidData->oid.data, oidData->oid.len);
+
+  priv_key.reset(PK11_GenerateKeyPair(slot.get(), CKM_EC_KEY_PAIR_GEN,
+                                      &ecParams, &pub_tmp, PR_FALSE, PR_FALSE,
+                                      nullptr));
+  ASSERT_NE(nullptr, priv_key);
+  ASSERT_NE(nullptr, pub_tmp);
+  pub_key.reset(pub_tmp);
+  rv = PK11_HPKE_SetupS(sender.get(), nullptr, nullptr, pub_key.get(),
+                        &info_item);
+  EXPECT_EQ(SECFailure, rv);
+  EXPECT_EQ(SEC_ERROR_BAD_KEY, PORT_GetError());
+}
+#else
+TEST(Pkcs11HpkeTest, EnsureNotImplemented) {
+  ScopedHpkeContext cx(
+      PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256,
+                           HpkeAeadChaCha20Poly1305, nullptr, nullptr));
+  EXPECT_FALSE(cx.get());
+  EXPECT_EQ(SEC_ERROR_INVALID_ALGORITHM, PORT_GetError());
+}
+#endif  // NSS_ENABLE_DRAFT_HPKE
+
+}  // namespace nss_test
diff --git a/mozilla/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc b/mozilla/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
index 6905ed0c0f9..dcede798cc0 100644
--- a/mozilla/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
+++ b/mozilla/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
@@ -348,6 +348,85 @@ TEST_F(TlsConnectStreamTls13, ChangeCipherSpecBeforeClientHelloTwice) {
   client_->CheckErrorCode(SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT);
 }
 
+// The server rejects a ChangeCipherSpec if the client advertises an
+// empty session ID.
+TEST_F(TlsConnectStreamTls13, ChangeCipherSpecAfterClientHelloEmptySid) {
+  EnsureTlsSetup();
+  ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+
+  StartConnect();
+  client_->Handshake();  // Send ClientHello
+  client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));  // Send CCS
+
+  server_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
+  server_->Handshake();  // Consume ClientHello and CCS
+  server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
+}
+
+// The server rejects multiple ChangeCipherSpec even if the client
+// indicates compatibility mode with non-empty session ID.
+TEST_F(Tls13CompatTest, ChangeCipherSpecAfterClientHelloTwice) {
+  EnsureTlsSetup();
+  ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+  EnableCompatMode();
+
+  StartConnect();
+  client_->Handshake();  // Send ClientHello
+  // Send CCS twice in a row
+  client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+  client_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+
+  server_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
+  server_->Handshake();  // Consume ClientHello and CCS.
+  server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
+}
+
+// The client rejects a ChangeCipherSpec if it advertises an empty
+// session ID.
+TEST_F(TlsConnectStreamTls13, ChangeCipherSpecAfterServerHelloEmptySid) {
+  EnsureTlsSetup();
+  ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+
+  // To replace Finished with a CCS below
+  auto filter = MakeTlsFilter<TlsHandshakeDropper>(server_);
+  filter->SetHandshakeTypes({kTlsHandshakeFinished});
+  filter->EnableDecryption();
+
+  StartConnect();
+  client_->Handshake();  // Send ClientHello
+  server_->Handshake();  // Consume ClientHello, and
+                         // send ServerHello..CertificateVerify
+  // Send CCS
+  server_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+  client_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
+  client_->Handshake();  // Consume ClientHello and CCS
+  client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
+}
+
+// The client rejects multiple ChangeCipherSpec in a row even if the
+// client indicates compatibility mode with non-empty session ID.
+TEST_F(Tls13CompatTest, ChangeCipherSpecAfterServerHelloTwice) {
+  EnsureTlsSetup();
+  ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3);
+  EnableCompatMode();
+
+  // To replace Finished with a CCS below
+  auto filter = MakeTlsFilter<TlsHandshakeDropper>(server_);
+  filter->SetHandshakeTypes({kTlsHandshakeFinished});
+  filter->EnableDecryption();
+
+  StartConnect();
+  client_->Handshake();  // Send ClientHello
+  server_->Handshake();  // Consume ClientHello, and
+                         // send ServerHello..CertificateVerify
+                         // the ServerHello is followed by CCS
+  // Send another CCS
+  server_->SendDirect(DataBuffer(kCannedCcs, sizeof(kCannedCcs)));
+  client_->ExpectSendAlert(kTlsAlertUnexpectedMessage);
+  client_->Handshake();  // Consume ClientHello and CCS
+  client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
+}
+
 // If we negotiate 1.2, we abort.
 TEST_F(TlsConnectStreamTls13, ChangeCipherSpecBeforeClientHello12) {
   EnsureTlsSetup();
diff --git a/mozilla/security/nss/lib/nss/nss.def b/mozilla/security/nss/lib/nss/nss.def
index 829a4447398..87492ec6040 100644
--- a/mozilla/security/nss/lib/nss/nss.def
+++ b/mozilla/security/nss/lib/nss/nss.def
@@ -522,7 +522,7 @@ VFY_EndWithSignature;
 ;+NSS_3.3.1 { 	# NSS 3.3.1 release
 ;+    global:
 ;+#
-;+# The following symbols are exported only to make libsmime3.so work. 
+;+# The following symbols are exported only to make libsmime3.so work.
 ;+# These are still private!!!
 ;+#
 PK11_CreatePBEParams;
@@ -1183,6 +1183,17 @@ SECMOD_GetSystemFIPSEnabled;
 ;+};
 ;+NSS_3.58 {   # NSS 3.58 release
 ;+    global:
+PK11_HPKE_DestroyContext;
+PK11_HPKE_Deserialize;
+PK11_HPKE_ExportSecret;
+PK11_HPKE_GetEncapPubKey;
+PK11_HPKE_NewContext;
+PK11_HPKE_Open;
+PK11_HPKE_Seal;
+PK11_HPKE_Serialize;
+PK11_HPKE_SetupS;
+PK11_HPKE_SetupR;
+PK11_HPKE_ValidateParameters;
 PK11_ImportDataKey;
 ;+    local:
 ;+       *;
diff --git a/mozilla/security/nss/lib/pk11wrap/exports.gyp b/mozilla/security/nss/lib/pk11wrap/exports.gyp
index 3650060804c..5067cade821 100644
--- a/mozilla/security/nss/lib/pk11wrap/exports.gyp
+++ b/mozilla/security/nss/lib/pk11wrap/exports.gyp
@@ -13,6 +13,7 @@
         {
           'files': [
             'pk11func.h',
+            'pk11hpke.h',
             'pk11pqg.h',
             'pk11priv.h',
             'pk11pub.h',
diff --git a/mozilla/security/nss/lib/pk11wrap/manifest.mn b/mozilla/security/nss/lib/pk11wrap/manifest.mn
index 385fa5e7b56..8f8a387b44b 100644
--- a/mozilla/security/nss/lib/pk11wrap/manifest.mn
+++ b/mozilla/security/nss/lib/pk11wrap/manifest.mn
@@ -1,4 +1,4 @@
-# 
+#
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
@@ -9,6 +9,7 @@ EXPORTS = \
 	secmodt.h \
 	secpkcs5.h \
 	pk11func.h \
+	pk11hpke.h \
 	pk11pub.h \
 	pk11priv.h \
 	pk11sdr.h \
@@ -30,6 +31,7 @@ CSRCS = \
 	pk11cert.c \
 	pk11cxt.c \
 	pk11err.c  \
+	pk11hpke.c \
 	pk11kea.c \
 	pk11list.c \
 	pk11load.c \
diff --git a/mozilla/security/nss/lib/pk11wrap/pk11hpke.c b/mozilla/security/nss/lib/pk11wrap/pk11hpke.c
new file mode 100644
index 00000000000..7f8fe3b1b09
--- /dev/null
+++ b/mozilla/security/nss/lib/pk11wrap/pk11hpke.c
@@ -0,0 +1,1085 @@
+/*
+ * draft-irtf-cfrg-hpke-05
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "keyhi.h"
+#include "pkcs11t.h"
+#include "pk11func.h"
+#include "pk11hpke.h"
+#include "pk11pqg.h"
+#include "secerr.h"
+#include "secitem.h"
+#include "secmod.h"
+#include "secmodi.h"
+#include "secmodti.h"
+#include "secutil.h"
+
+#ifndef NSS_ENABLE_DRAFT_HPKE
+/* "Not Implemented" stubs to maintain the ABI. */
+SECStatus
+PK11_HPKE_ValidateParameters(HpkeKemId kemId, HpkeKdfId kdfId, HpkeAeadId aeadId)
+{
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+    return SECFailure;
+}
+HpkeContext *
+PK11_HPKE_NewContext(HpkeKemId kemId, HpkeKdfId kdfId, HpkeAeadId aeadId,
+                     PK11SymKey *psk, const SECItem *pskId)
+{
+
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+    return NULL;
+}
+SECStatus
+PK11_HPKE_Deserialize(const HpkeContext *cx, const PRUint8 *enc,
+                      unsigned int encLen, SECKEYPublicKey **outPubKey)
+{
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+    return SECFailure;
+}
+void
+PK11_HPKE_DestroyContext(HpkeContext *cx, PRBool freeit)
+{
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+}
+const SECItem *
+PK11_HPKE_GetEncapPubKey(const HpkeContext *cx)
+{
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+    return NULL;
+}
+SECStatus
+PK11_HPKE_ExportSecret(const HpkeContext *cx, const SECItem *info,
+                       unsigned int L, PK11SymKey **outKey)
+{
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+    return SECFailure;
+}
+SECStatus
+PK11_HPKE_Open(HpkeContext *cx, const SECItem *aad, const SECItem *ct,
+               SECItem **outPt)
+{
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+    return SECFailure;
+}
+SECStatus
+PK11_HPKE_Seal(HpkeContext *cx, const SECItem *aad, const SECItem *pt, SECItem **outCt)
+{
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+    return SECFailure;
+}
+SECStatus
+PK11_HPKE_Serialize(const SECKEYPublicKey *pk, PRUint8 *buf, unsigned int *len, unsigned int maxLen)
+{
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+    return SECFailure;
+}
+SECStatus
+PK11_HPKE_SetupS(HpkeContext *cx, const SECKEYPublicKey *pkE, SECKEYPrivateKey *skE,
+                 SECKEYPublicKey *pkR, const SECItem *info)
+{
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+    return SECFailure;
+}
+SECStatus
+PK11_HPKE_SetupR(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey *skR,
+                 const SECItem *enc, const SECItem *info)
+{
+    PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+    return SECFailure;
+}
+
+#else
+static const char *DRAFT_LABEL = "HPKE-05 ";
+static const char *EXP_LABEL = "exp";
+static const char *HPKE_LABEL = "HPKE";
+static const char *INFO_LABEL = "info_hash";
+static const char *KEM_LABEL = "KEM";
+static const char *KEY_LABEL = "key";
+static const char *NONCE_LABEL = "nonce";
+static const char *PSK_ID_LABEL = "psk_id_hash";
+static const char *PSK_LABEL = "psk_hash";
+static const char *SECRET_LABEL = "secret";
+static const char *SEC_LABEL = "sec";
+static const char *EAE_PRK_LABEL = "eae_prk";
+static const char *SH_SEC_LABEL = "shared_secret";
+
+struct HpkeContextStr {
+    const hpkeKemParams *kemParams;
+    const hpkeKdfParams *kdfParams;
+    const hpkeAeadParams *aeadParams;
+    PRUint8 mode;               /* Base and PSK modes supported. */
+    SECItem *encapPubKey;       /* Marshalled public key, sent to receiver. */
+    SECItem *nonce;             /* Deterministic nonce for AEAD. */
+    SECItem *pskId;             /* PSK identifier (non-secret). */
+    PK11Context *aeadContext;   /* AEAD context used by Seal/Open. */
+    PRUint64 sequenceNumber;    /* seqNo for decrypt IV construction. */
+    PK11SymKey *sharedSecret;   /* ExtractAndExpand output key. */
+    PK11SymKey *key;            /* Key used with the AEAD. */
+    PK11SymKey *exporterSecret; /* Derivation key for ExportSecret. */
+    PK11SymKey *psk;            /* PSK imported by the application. */
+};
+
+static const hpkeKemParams kemParams[] = {
+    /* KEM, Nsk, Nsecret, Npk, oidTag, Hash mechanism  */
+    { HpkeDhKemX25519Sha256, 32, 32, 32, SEC_OID_CURVE25519, CKM_SHA256 },
+};
+
+static const hpkeKdfParams kdfParams[] = {
+    /* KDF, Nh, mechanism  */
+    { HpkeKdfHkdfSha256, SHA256_LENGTH, CKM_SHA256 },
+};
+static const hpkeAeadParams aeadParams[] = {
+    /* AEAD, Nk, Nn, tagLen, mechanism  */
+    { HpkeAeadAes128Gcm, 16, 12, 16, CKM_AES_GCM },
+    { HpkeAeadChaCha20Poly1305, 32, 12, 16, CKM_CHACHA20_POLY1305 },
+};
+
+static inline const hpkeKemParams *
+kemId2Params(HpkeKemId kemId)
+{
+    switch (kemId) {
+        case HpkeDhKemX25519Sha256:
+            return &kemParams[0];
+        default:
+            return NULL;
+    }
+}
+
+static inline const hpkeKdfParams *
+kdfId2Params(HpkeKdfId kdfId)
+{
+    switch (kdfId) {
+        case HpkeKdfHkdfSha256:
+            return &kdfParams[0];
+        default:
+            return NULL;
+    }
+}
+
+static const inline hpkeAeadParams *
+aeadId2Params(HpkeAeadId aeadId)
+{
+    switch (aeadId) {
+        case HpkeAeadAes128Gcm:
+            return &aeadParams[0];
+        case HpkeAeadChaCha20Poly1305:
+            return &aeadParams[1];
+        default:
+            return NULL;
+    }
+}
+
+static SECStatus
+encodeShort(PRUint32 val, PRUint8 *b)
+{
+    if (val > 0xFFFF || !b) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+    b[0] = (val >> 8) & 0xff;
+    b[1] = val & 0xff;
+    return SECSuccess;
+}
+
+SECStatus
+PK11_HPKE_ValidateParameters(HpkeKemId kemId, HpkeKdfId kdfId, HpkeAeadId aeadId)
+{
+    /* If more variants are added, ensure the combination is also
+     * legal. For now it is, since only the AEAD may vary. */
+    const hpkeKemParams *kem = kemId2Params(kemId);
+    const hpkeKdfParams *kdf = kdfId2Params(kdfId);
+    const hpkeAeadParams *aead = aeadId2Params(aeadId);
+    if (!kem || !kdf || !aead) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+    return SECSuccess;
+}
+
+HpkeContext *
+PK11_HPKE_NewContext(HpkeKemId kemId, HpkeKdfId kdfId, HpkeAeadId aeadId,
+                     PK11SymKey *psk, const SECItem *pskId)
+{
+    SECStatus rv = SECSuccess;
+    PK11SlotInfo *slot = NULL;
+    HpkeContext *cx = NULL;
+    /* Both the PSK and the PSK ID default to empty. */
+    SECItem emptyItem = { siBuffer, NULL, 0 };
+
+    cx = PORT_ZNew(HpkeContext);
+    if (!cx) {
+        return NULL;
+    }
+    cx->mode = psk ? HpkeModePsk : HpkeModeBase;
+    cx->kemParams = kemId2Params(kemId);
+    cx->kdfParams = kdfId2Params(kdfId);
+    cx->aeadParams = aeadId2Params(aeadId);
+    CHECK_FAIL_ERR((!!psk != !!pskId), SEC_ERROR_INVALID_ARGS);
+    CHECK_FAIL_ERR(!cx->kemParams || !cx->kdfParams || !cx->aeadParams,
+                   SEC_ERROR_INVALID_ARGS);
+
+    /* Import the provided PSK or the default. */
+    slot = PK11_GetBestSlot(CKM_EC_KEY_PAIR_GEN, NULL);
+    CHECK_FAIL(!slot);
+    if (psk) {
+        cx->psk = PK11_ReferenceSymKey(psk);
+        cx->pskId = SECITEM_DupItem(pskId);
+    } else {
+        cx->psk = PK11_ImportDataKey(slot, CKM_HKDF_DATA, PK11_OriginUnwrap,
+                                     CKA_DERIVE, &emptyItem, NULL);
+        cx->pskId = SECITEM_DupItem(&emptyItem);
+    }
+    CHECK_FAIL(!cx->psk);
+    CHECK_FAIL(!cx->pskId);
+
+CLEANUP:
+    if (rv != SECSuccess) {
+        PK11_FreeSymKey(cx->psk);
+        SECITEM_FreeItem(cx->pskId, PR_TRUE);
+        cx->pskId = NULL;
+        cx->psk = NULL;
+        PORT_Free(cx);
+        cx = NULL;
+    }
+    if (slot) {
+        PK11_FreeSlot(slot);
+    }
+    return cx;
+}
+
+void
+PK11_HPKE_DestroyContext(HpkeContext *cx, PRBool freeit)
+{
+    if (!cx) {
+        return;
+    }
+
+    if (cx->aeadContext) {
+        PK11_DestroyContext((PK11Context *)cx->aeadContext, PR_TRUE);
+        cx->aeadContext = NULL;
+    }
+    PK11_FreeSymKey(cx->exporterSecret);
+    PK11_FreeSymKey(cx->sharedSecret);
+    PK11_FreeSymKey(cx->key);
+    PK11_FreeSymKey(cx->psk);
+    SECITEM_FreeItem(cx->pskId, PR_TRUE);
+    SECITEM_FreeItem(cx->nonce, PR_TRUE);
+    SECITEM_FreeItem(cx->encapPubKey, PR_TRUE);
+    cx->exporterSecret = NULL;
+    cx->sharedSecret = NULL;
+    cx->key = NULL;
+    cx->psk = NULL;
+    cx->pskId = NULL;
+    cx->nonce = NULL;
+    cx->encapPubKey = NULL;
+    if (freeit) {
+        PORT_ZFree(cx, sizeof(HpkeContext));
+    }
+}
+
+SECStatus
+PK11_HPKE_Serialize(const SECKEYPublicKey *pk, PRUint8 *buf, unsigned int *len, unsigned int maxLen)
+{
+    if (!pk || !len || pk->keyType != ecKey) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    /* If no buffer provided, return the length required for
+     * the serialized public key. */
+    if (!buf) {
+        *len = pk->u.ec.publicValue.len;
+        return SECSuccess;
+    }
+
+    if (maxLen < pk->u.ec.publicValue.len) {
+        PORT_SetError(SEC_ERROR_INPUT_LEN);
+        return SECFailure;
+    }
+
+    PORT_Memcpy(buf, pk->u.ec.publicValue.data, pk->u.ec.publicValue.len);
+    *len = pk->u.ec.publicValue.len;
+    return SECSuccess;
+};
+
+SECStatus
+PK11_HPKE_Deserialize(const HpkeContext *cx, const PRUint8 *enc,
+                      unsigned int encLen, SECKEYPublicKey **outPubKey)
+{
+    SECStatus rv;
+    SECKEYPublicKey *pubKey = NULL;
+    SECOidData *oidData = NULL;
+    PLArenaPool *arena;
+
+    if (!cx || !enc || encLen == 0 || !outPubKey) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    CHECK_FAIL(!arena);
+    pubKey = PORT_ArenaZNew(arena, SECKEYPublicKey);
+    CHECK_FAIL(!pubKey);
+
+    pubKey->arena = arena;
+    pubKey->keyType = ecKey;
+    pubKey->pkcs11Slot = NULL;
+    pubKey->pkcs11ID = CK_INVALID_HANDLE;
+
+    rv = SECITEM_MakeItem(pubKey->arena, &pubKey->u.ec.publicValue,
+                          enc, encLen);
+    CHECK_RV(rv);
+    pubKey->u.ec.encoding = ECPoint_Undefined;
+    pubKey->u.ec.size = 0;
+
+    oidData = SECOID_FindOIDByTag(cx->kemParams->oidTag);
+    CHECK_FAIL_ERR(!oidData, SEC_ERROR_INVALID_ALGORITHM);
+
+    // Create parameters.
+    CHECK_FAIL(!SECITEM_AllocItem(pubKey->arena, &pubKey->u.ec.DEREncodedParams,
+                                  2 + oidData->oid.len));
+
+    // Set parameters.
+    pubKey->u.ec.DEREncodedParams.data[0] = SEC_ASN1_OBJECT_ID;
+    pubKey->u.ec.DEREncodedParams.data[1] = oidData->oid.len;
+    memcpy(pubKey->u.ec.DEREncodedParams.data + 2, oidData->oid.data, oidData->oid.len);
+    *outPubKey = pubKey;
+
+CLEANUP:
+    if (rv != SECSuccess) {
+        SECKEY_DestroyPublicKey(pubKey);
+    }
+    return rv;
+};
+
+static SECStatus
+pk11_hpke_CheckKeys(const HpkeContext *cx, const SECKEYPublicKey *pk,
+                    const SECKEYPrivateKey *sk)
+{
+    SECOidTag pkTag;
+    unsigned int i;
+    if (pk->keyType != ecKey || (sk && sk->keyType != ecKey)) {
+        PORT_SetError(SEC_ERROR_BAD_KEY);
+        return SECFailure;
+    }
+    pkTag = SECKEY_GetECCOid(&pk->u.ec.DEREncodedParams);
+    if (pkTag != cx->kemParams->oidTag) {
+        PORT_SetError(SEC_ERROR_BAD_KEY);
+        return SECFailure;
+    }
+    for (i = 0; i < PR_ARRAY_SIZE(kemParams); i++) {
+        if (cx->kemParams->oidTag == kemParams[i].oidTag) {
+            return SECSuccess;
+        }
+    }
+
+    return SECFailure;
+}
+
+static SECStatus
+pk11_hpke_GenerateKeyPair(const HpkeContext *cx, SECKEYPublicKey **pkE,
+                          SECKEYPrivateKey **skE)
+{
+    SECStatus rv = SECSuccess;
+    SECKEYPrivateKey *privKey = NULL;
+    SECKEYPublicKey *pubKey = NULL;
+    SECOidData *oidData = NULL;
+    SECKEYECParams ecp;
+    PK11SlotInfo *slot = NULL;
+    ecp.data = NULL;
+    PORT_Assert(cx && skE && pkE);
+
+    oidData = SECOID_FindOIDByTag(cx->kemParams->oidTag);
+    CHECK_FAIL_ERR(!oidData, SEC_ERROR_INVALID_ALGORITHM);
+    ecp.data = PORT_Alloc(2 + oidData->oid.len);
+    CHECK_FAIL(!ecp.data);
+
+    ecp.len = 2 + oidData->oid.len;
+    ecp.type = siDEROID;
+    ecp.data[0] = SEC_ASN1_OBJECT_ID;
+    ecp.data[1] = oidData->oid.len;
+    memcpy(&ecp.data[2], oidData->oid.data, oidData->oid.len);
+
+    slot = PK11_GetBestSlot(CKM_EC_KEY_PAIR_GEN, NULL);
+    CHECK_FAIL(!slot);
+
+    privKey = PK11_GenerateKeyPair(slot, CKM_EC_KEY_PAIR_GEN, &ecp, &pubKey,
+                                   PR_FALSE, PR_TRUE, NULL);
+    CHECK_FAIL_ERR((!privKey || !pubKey), SEC_ERROR_KEYGEN_FAIL);
+    PORT_Assert(rv == SECSuccess);
+    *skE = privKey;
+    *pkE = pubKey;
+
+CLEANUP:
+    if (rv != SECSuccess) {
+        SECKEY_DestroyPrivateKey(privKey);
+        SECKEY_DestroyPublicKey(pubKey);
+    }
+    if (slot) {
+        PK11_FreeSlot(slot);
+    }
+    PORT_Free(ecp.data);
+    return rv;
+}
+
+static inline SECItem *
+pk11_hpke_MakeExtractLabel(const char *prefix, unsigned int prefixLen,
+                           const char *label, unsigned int labelLen,
+                           const SECItem *suiteId, const SECItem *ikm)
+{
+    SECItem *out = NULL;
+    size_t off = 0;
+    out = SECITEM_AllocItem(NULL, NULL, prefixLen + labelLen + suiteId->len + (ikm ? ikm->len : 0));
+    if (!out) {
+        return NULL;
+    }
+
+    memcpy(&out->data[off], prefix, prefixLen);
+    off += prefixLen;
+    memcpy(&out->data[off], suiteId->data, suiteId->len);
+    off += suiteId->len;
+    memcpy(&out->data[off], label, labelLen);
+    off += labelLen;
+    if (ikm && ikm->data) {
+        memcpy(&out->data[off], ikm->data, ikm->len);
+        off += ikm->len;
+    }
+
+    return out;
+}
+
+static SECStatus
+pk11_hpke_LabeledExtractData(const HpkeContext *cx, SECItem *salt,
+                             const SECItem *suiteId, const char *label,
+                             unsigned int labelLen, const SECItem *ikm, SECItem **out)
+{
+    SECStatus rv;
+    CK_HKDF_PARAMS params = { 0 };
+    PK11SymKey *importedIkm = NULL;
+    PK11SymKey *prk = NULL;
+    PK11SlotInfo *slot = NULL;
+    SECItem *borrowed;
+    SECItem *outDerived = NULL;
+    SECItem *labeledIkm;
+    SECItem paramsItem = { siBuffer, (unsigned char *)&params,
+                           sizeof(params) };
+    PORT_Assert(cx && ikm && label && labelLen && out && suiteId);
+
+    labeledIkm = pk11_hpke_MakeExtractLabel(DRAFT_LABEL, strlen(DRAFT_LABEL), label, labelLen, suiteId, ikm);
+    CHECK_FAIL(!labeledIkm);
+    params.bExtract = CK_TRUE;
+    params.bExpand = CK_FALSE;
+    params.prfHashMechanism = cx->kemParams->hashMech;
+    params.ulSaltType = salt ? CKF_HKDF_SALT_DATA : CKF_HKDF_SALT_NULL;
+    params.pSalt = salt ? (CK_BYTE_PTR)salt->data : NULL;
+    params.ulSaltLen = salt ? salt->len : 0;
+    params.pInfo = labeledIkm->data;
+    params.ulInfoLen = labeledIkm->len;
+
+    slot = PK11_GetBestSlot(CKM_EC_KEY_PAIR_GEN, NULL);
+    CHECK_FAIL(!slot);
+
+    importedIkm = PK11_ImportDataKey(slot, CKM_HKDF_DATA, PK11_OriginUnwrap,
+                                     CKA_DERIVE, labeledIkm, NULL);
+    CHECK_FAIL(!importedIkm);
+    prk = PK11_Derive(importedIkm, CKM_HKDF_DATA, &paramsItem,
+                      CKM_HKDF_DERIVE, CKA_DERIVE, 0);
+    CHECK_FAIL(!prk);
+    rv = PK11_ExtractKeyValue(prk);
+    CHECK_RV(rv);
+    borrowed = PK11_GetKeyData(prk);
+    CHECK_FAIL(!borrowed);
+    outDerived = SECITEM_DupItem(borrowed);
+    CHECK_FAIL(!outDerived);
+
+    *out = outDerived;
+
+CLEANUP:
+    PK11_FreeSymKey(importedIkm);
+    PK11_FreeSymKey(prk);
+    SECITEM_FreeItem(labeledIkm, PR_TRUE);
+    if (slot) {
+        PK11_FreeSlot(slot);
+    }
+    return rv;
+}
+
+static SECStatus
+pk11_hpke_LabeledExtract(const HpkeContext *cx, PK11SymKey *salt,
+                         const SECItem *suiteId, const char *label,
+                         unsigned int labelLen, PK11SymKey *ikm, PK11SymKey **out)
+{
+    SECStatus rv = SECSuccess;
+    SECItem *innerLabel = NULL;
+    PK11SymKey *labeledIkm = NULL;
+    PK11SymKey *prk = NULL;
+    CK_HKDF_PARAMS params = { 0 };
+    CK_KEY_DERIVATION_STRING_DATA labelData;
+    SECItem labelDataItem = { siBuffer, NULL, 0 };
+    SECItem paramsItem = { siBuffer, (unsigned char *)&params,
+                           sizeof(params) };
+    PORT_Assert(cx && ikm && label && labelLen && out && suiteId);
+
+    innerLabel = pk11_hpke_MakeExtractLabel(DRAFT_LABEL, strlen(DRAFT_LABEL), label, labelLen, suiteId, NULL);
+    CHECK_FAIL(!innerLabel);
+    labelData.pData = innerLabel->data;
+    labelData.ulLen = innerLabel->len;
+    labelDataItem.data = (PRUint8 *)&labelData;
+    labelDataItem.len = sizeof(labelData);
+    labeledIkm = PK11_Derive(ikm, CKM_CONCATENATE_DATA_AND_BASE,
+                             &labelDataItem, CKM_GENERIC_SECRET_KEY_GEN, CKA_DERIVE, 0);
+    CHECK_FAIL(!labeledIkm);
+
+    params.bExtract = CK_TRUE;
+    params.bExpand = CK_FALSE;
+    params.prfHashMechanism = cx->kemParams->hashMech;
+    params.ulSaltType = salt ? CKF_HKDF_SALT_KEY : CKF_HKDF_SALT_NULL;
+    params.hSaltKey = salt ? PK11_GetSymKeyHandle(salt) : CK_INVALID_HANDLE;
+
+    prk = PK11_Derive(labeledIkm, CKM_HKDF_DERIVE, &paramsItem,
+                      CKM_HKDF_DERIVE, CKA_DERIVE, 0);
+    CHECK_FAIL(!prk);
+    *out = prk;
+
+CLEANUP:
+    PK11_FreeSymKey(labeledIkm);
+    SECITEM_ZfreeItem(innerLabel, PR_TRUE);
+    return rv;
+}
+
+static SECStatus
+pk11_hpke_LabeledExpand(const HpkeContext *cx, PK11SymKey *prk, const SECItem *suiteId,
+                        const char *label, unsigned int labelLen, const SECItem *info,
+                        unsigned int L, PK11SymKey **outKey, SECItem **outItem)
+{
+    SECStatus rv;
+    CK_MECHANISM_TYPE keyMech;
+    CK_MECHANISM_TYPE deriveMech;
+    CK_HKDF_PARAMS params = { 0 };
+    PK11SymKey *derivedKey = NULL;
+    SECItem *labeledInfoItem = NULL;
+    SECItem paramsItem = { siBuffer, (unsigned char *)&params,
+                           sizeof(params) };
+    SECItem *derivedKeyData;
+    PRUint8 encodedL[2];
+    size_t off = 0;
+    size_t len;
+    PORT_Assert(cx && prk && label && (!!outKey != !!outItem));
+
+    rv = encodeShort(L, encodedL);
+    CHECK_RV(rv);
+
+    len = info ? info->len : 0;
+    len += sizeof(encodedL) + strlen(DRAFT_LABEL) + suiteId->len + labelLen;
+    labeledInfoItem = SECITEM_AllocItem(NULL, NULL, len);
+    CHECK_FAIL(!labeledInfoItem);
+
+    memcpy(&labeledInfoItem->data[off], encodedL, sizeof(encodedL));
+    off += sizeof(encodedL);
+    memcpy(&labeledInfoItem->data[off], DRAFT_LABEL, strlen(DRAFT_LABEL));
+    off += strlen(DRAFT_LABEL);
+    memcpy(&labeledInfoItem->data[off], suiteId->data, suiteId->len);
+    off += suiteId->len;
+    memcpy(&labeledInfoItem->data[off], label, labelLen);
+    off += labelLen;
+    if (info) {
+        memcpy(&labeledInfoItem->data[off], info->data, info->len);
+        off += info->len;
+    }
+
+    params.bExtract = CK_FALSE;
+    params.bExpand = CK_TRUE;
+    params.prfHashMechanism = cx->kemParams->hashMech;
+    params.ulSaltType = CKF_HKDF_SALT_NULL;
+    params.pInfo = labeledInfoItem->data;
+    params.ulInfoLen = labeledInfoItem->len;
+    deriveMech = outItem ? CKM_HKDF_DATA : CKM_HKDF_DERIVE;
+    /* If we're expanding to the encryption key use the appropriate mechanism. */
+    keyMech = (label && !strcmp(KEY_LABEL, label)) ? cx->aeadParams->mech : CKM_HKDF_DERIVE;
+
+    derivedKey = PK11_Derive(prk, deriveMech, &paramsItem, keyMech, CKA_DERIVE, L);
+    CHECK_FAIL(!derivedKey);
+
+    if (outItem) {
+        /* Don't allow export of real keys. */
+        CHECK_FAIL_ERR(deriveMech != CKM_HKDF_DATA, SEC_ERROR_LIBRARY_FAILURE);
+        rv = PK11_ExtractKeyValue(derivedKey);
+        CHECK_RV(rv);
+        derivedKeyData = PK11_GetKeyData(derivedKey);
+        CHECK_FAIL_ERR((!derivedKeyData), SEC_ERROR_NO_KEY);
+        *outItem = SECITEM_DupItem(derivedKeyData);
+        CHECK_FAIL(!*outItem);
+        PK11_FreeSymKey(derivedKey);
+    } else {
+        *outKey = derivedKey;
+    }
+
+CLEANUP:
+    if (rv != SECSuccess) {
+        PK11_FreeSymKey(derivedKey);
+    }
+    SECITEM_ZfreeItem(labeledInfoItem, PR_TRUE);
+    return rv;
+}
+
+static SECStatus
+pk11_hpke_ExtractAndExpand(const HpkeContext *cx, PK11SymKey *ikm,
+                           const SECItem *kemContext, PK11SymKey **out)
+{
+    SECStatus rv;
+    PK11SymKey *eaePrk = NULL;
+    PK11SymKey *sharedSecret = NULL;
+    PRUint8 suiteIdBuf[5];
+    PORT_Memcpy(suiteIdBuf, KEM_LABEL, strlen(KEM_LABEL));
+    SECItem suiteIdItem = { siBuffer, suiteIdBuf, sizeof(suiteIdBuf) };
+    PORT_Assert(cx && ikm && kemContext && out);
+
+    rv = encodeShort(cx->kemParams->id, &suiteIdBuf[3]);
+    CHECK_RV(rv);
+
+    rv = pk11_hpke_LabeledExtract(cx, NULL, &suiteIdItem, EAE_PRK_LABEL,
+                                  strlen(EAE_PRK_LABEL), ikm, &eaePrk);
+    CHECK_RV(rv);
+
+    rv = pk11_hpke_LabeledExpand(cx, eaePrk, &suiteIdItem, SH_SEC_LABEL, strlen(SH_SEC_LABEL),
+                                 kemContext, cx->kemParams->Nsecret, &sharedSecret, NULL);
+    CHECK_RV(rv);
+    *out = sharedSecret;
+
+CLEANUP:
+    if (rv != SECSuccess) {
+        PK11_FreeSymKey(sharedSecret);
+    }
+    PK11_FreeSymKey(eaePrk);
+    return rv;
+}
+
+static SECStatus
+pk11_hpke_Encap(HpkeContext *cx, const SECKEYPublicKey *pkE, SECKEYPrivateKey *skE,
+                SECKEYPublicKey *pkR)
+{
+    SECStatus rv;
+    PK11SymKey *dh = NULL;
+    SECItem *kemContext = NULL;
+    SECItem *encPkR = NULL;
+    unsigned int tmpLen;
+
+    PORT_Assert(cx && skE && pkE && pkR);
+
+    rv = pk11_hpke_CheckKeys(cx, pkE, skE);
+    CHECK_RV(rv);
+    rv = pk11_hpke_CheckKeys(cx, pkR, NULL);
+    CHECK_RV(rv);
+
+    dh = PK11_PubDeriveWithKDF(skE, pkR, PR_FALSE, NULL, NULL, CKM_ECDH1_DERIVE,
+                               CKM_SHA512_HMAC /* unused */, CKA_DERIVE, 0,
+                               CKD_NULL, NULL, NULL);
+    CHECK_FAIL(!dh);
+
+    /* Encapsulate our sender public key. Many use cases
+     * (including ECH) require that the application fetch
+     * this value, so do it once and store into the cx. */
+    rv = PK11_HPKE_Serialize(pkE, NULL, &tmpLen, 0);
+    CHECK_RV(rv);
+    cx->encapPubKey = SECITEM_AllocItem(NULL, NULL, tmpLen);
+    CHECK_FAIL(!cx->encapPubKey);
+    rv = PK11_HPKE_Serialize(pkE, cx->encapPubKey->data,
+                             &cx->encapPubKey->len, cx->encapPubKey->len);
+    CHECK_RV(rv);
+
+    rv = PK11_HPKE_Serialize(pkR, NULL, &tmpLen, 0);
+    CHECK_RV(rv);
+
+    kemContext = SECITEM_AllocItem(NULL, NULL, cx->encapPubKey->len + tmpLen);
+    CHECK_FAIL(!kemContext);
+
+    memcpy(kemContext->data, cx->encapPubKey->data, cx->encapPubKey->len);
+    rv = PK11_HPKE_Serialize(pkR, &kemContext->data[cx->encapPubKey->len], &tmpLen, tmpLen);
+    CHECK_RV(rv);
+
+    rv = pk11_hpke_ExtractAndExpand(cx, dh, kemContext, &cx->sharedSecret);
+    CHECK_RV(rv);
+
+CLEANUP:
+    if (rv != SECSuccess) {
+        PK11_FreeSymKey(cx->sharedSecret);
+        cx->sharedSecret = NULL;
+    }
+    SECITEM_FreeItem(encPkR, PR_TRUE);
+    SECITEM_FreeItem(kemContext, PR_TRUE);
+    PK11_FreeSymKey(dh);
+    return rv;
+}
+
+SECStatus
+PK11_HPKE_ExportSecret(const HpkeContext *cx, const SECItem *info, unsigned int L,
+                       PK11SymKey **out)
+{
+    SECStatus rv;
+    PK11SymKey *exported;
+    PRUint8 suiteIdBuf[10];
+    PORT_Memcpy(suiteIdBuf, HPKE_LABEL, strlen(HPKE_LABEL));
+    SECItem suiteIdItem = { siBuffer, suiteIdBuf, sizeof(suiteIdBuf) };
+
+    /* Arbitrary info length limit well under the specified max. */
+    if (!cx || !info || (!info->data && info->len) || info->len > 0xFFFF ||
+        !L || (L > 255 * cx->kdfParams->Nh)) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    rv = encodeShort(cx->kemParams->id, &suiteIdBuf[4]);
+    CHECK_RV(rv);
+    rv = encodeShort(cx->kdfParams->id, &suiteIdBuf[6]);
+    CHECK_RV(rv);
+    rv = encodeShort(cx->aeadParams->id, &suiteIdBuf[8]);
+    CHECK_RV(rv);
+
+    rv = pk11_hpke_LabeledExpand(cx, cx->exporterSecret, &suiteIdItem, SEC_LABEL,
+                                 strlen(SEC_LABEL), info, L, &exported, NULL);
+    CHECK_RV(rv);
+    *out = exported;
+
+CLEANUP:
+    return rv;
+}
+
+static SECStatus
+pk11_hpke_Decap(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey *skR,
+                const SECItem *encS)
+{
+    SECStatus rv;
+    PK11SymKey *dh = NULL;
+    SECItem *encR = NULL;
+    SECItem *kemContext = NULL;
+    SECKEYPublicKey *pkS = NULL;
+    unsigned int tmpLen;
+
+    if (!cx || !skR || !pkR || !encS || !encS->data || !encS->len) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    rv = PK11_HPKE_Deserialize(cx, encS->data, encS->len, &pkS);
+    CHECK_RV(rv);
+
+    rv = pk11_hpke_CheckKeys(cx, pkR, skR);
+    CHECK_RV(rv);
+    rv = pk11_hpke_CheckKeys(cx, pkS, NULL);
+    CHECK_RV(rv);
+
+    dh = PK11_PubDeriveWithKDF(skR, pkS, PR_FALSE, NULL, NULL, CKM_ECDH1_DERIVE,
+                               CKM_SHA512_HMAC /* unused */, CKA_DERIVE, 0,
+                               CKD_NULL, NULL, NULL);
+    CHECK_FAIL(!dh);
+
+    /* kem_context = concat(enc, pkRm) */
+    rv = PK11_HPKE_Serialize(pkR, NULL, &tmpLen, 0);
+    CHECK_RV(rv);
+
+    kemContext = SECITEM_AllocItem(NULL, NULL, encS->len + tmpLen);
+    CHECK_FAIL(!kemContext);
+
+    memcpy(kemContext->data, encS->data, encS->len);
+    rv = PK11_HPKE_Serialize(pkR, &kemContext->data[encS->len], &tmpLen,
+                             kemContext->len - encS->len);
+    CHECK_RV(rv);
+    rv = pk11_hpke_ExtractAndExpand(cx, dh, kemContext, &cx->sharedSecret);
+    CHECK_RV(rv);
+CLEANUP:
+    if (rv != SECSuccess) {
+        PK11_FreeSymKey(cx->sharedSecret);
+        cx->sharedSecret = NULL;
+    }
+    PK11_FreeSymKey(dh);
+    SECKEY_DestroyPublicKey(pkS);
+    SECITEM_FreeItem(encR, PR_TRUE);
+    SECITEM_ZfreeItem(kemContext, PR_TRUE);
+    return rv;
+}
+
+const SECItem *
+PK11_HPKE_GetEncapPubKey(const HpkeContext *cx)
+{
+    if (!cx) {
+        return NULL;
+    }
+    /* Will be NULL on receiver. */
+    return cx->encapPubKey;
+}
+
+static SECStatus
+pk11_hpke_KeySchedule(HpkeContext *cx, const SECItem *info)
+{
+    SECStatus rv;
+    SECItem contextItem = { siBuffer, NULL, 0 };
+    unsigned int len;
+    unsigned int off;
+    PK11SymKey *pskHash = NULL;
+    PK11SymKey *secret = NULL;
+    SECItem *pskIdHash = NULL;
+    SECItem *infoHash = NULL;
+    PRUint8 suiteIdBuf[10];
+    PORT_Memcpy(suiteIdBuf, HPKE_LABEL, strlen(HPKE_LABEL));
+    SECItem suiteIdItem = { siBuffer, suiteIdBuf, sizeof(suiteIdBuf) };
+    PORT_Assert(cx && info && cx->psk && cx->pskId);
+
+    rv = encodeShort(cx->kemParams->id, &suiteIdBuf[4]);
+    CHECK_RV(rv);
+    rv = encodeShort(cx->kdfParams->id, &suiteIdBuf[6]);
+    CHECK_RV(rv);
+    rv = encodeShort(cx->aeadParams->id, &suiteIdBuf[8]);
+    CHECK_RV(rv);
+
+    rv = pk11_hpke_LabeledExtractData(cx, NULL, &suiteIdItem, PSK_ID_LABEL,
+                                      strlen(PSK_ID_LABEL), cx->pskId, &pskIdHash);
+    CHECK_RV(rv);
+    rv = pk11_hpke_LabeledExtractData(cx, NULL, &suiteIdItem, INFO_LABEL,
+                                      strlen(INFO_LABEL), info, &infoHash);
+    CHECK_RV(rv);
+
+    // Make the context string
+    len = sizeof(cx->mode) + pskIdHash->len + infoHash->len;
+    CHECK_FAIL(!SECITEM_AllocItem(NULL, &contextItem, len));
+    off = 0;
+    memcpy(&contextItem.data[off], &cx->mode, sizeof(cx->mode));
+    off += sizeof(cx->mode);
+    memcpy(&contextItem.data[off], pskIdHash->data, pskIdHash->len);
+    off += pskIdHash->len;
+    memcpy(&contextItem.data[off], infoHash->data, infoHash->len);
+    off += infoHash->len;
+
+    // Compute the keys
+    rv = pk11_hpke_LabeledExtract(cx, NULL, &suiteIdItem, PSK_LABEL,
+                                  strlen(PSK_LABEL), cx->psk, &pskHash);
+    CHECK_RV(rv);
+    rv = pk11_hpke_LabeledExtract(cx, pskHash, &suiteIdItem, SECRET_LABEL,
+                                  strlen(SECRET_LABEL), cx->sharedSecret, &secret);
+    CHECK_RV(rv);
+    rv = pk11_hpke_LabeledExpand(cx, secret, &suiteIdItem, KEY_LABEL, strlen(KEY_LABEL),
+                                 &contextItem, cx->aeadParams->Nk, &cx->key, NULL);
+    CHECK_RV(rv);
+    rv = pk11_hpke_LabeledExpand(cx, secret, &suiteIdItem, NONCE_LABEL, strlen(NONCE_LABEL),
+                                 &contextItem, cx->aeadParams->Nn, NULL, &cx->nonce);
+    CHECK_RV(rv);
+    rv = pk11_hpke_LabeledExpand(cx, secret, &suiteIdItem, EXP_LABEL, strlen(EXP_LABEL),
+                                 &contextItem, cx->kdfParams->Nh, &cx->exporterSecret, NULL);
+    CHECK_RV(rv);
+
+CLEANUP:
+    /* If !SECSuccess, callers will tear down the context. */
+    PK11_FreeSymKey(pskHash);
+    PK11_FreeSymKey(secret);
+    SECITEM_FreeItem(&contextItem, PR_FALSE);
+    SECITEM_FreeItem(infoHash, PR_TRUE);
+    SECITEM_FreeItem(pskIdHash, PR_TRUE);
+    return rv;
+}
+
+SECStatus
+PK11_HPKE_SetupR(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey *skR,
+                 const SECItem *enc, const SECItem *info)
+{
+    SECStatus rv;
+    SECItem nullParams = { siBuffer, NULL, 0 };
+
+    CHECK_FAIL_ERR((!cx || !skR || !info || !enc || !enc->data || !enc->len),
+                   SEC_ERROR_INVALID_ARGS);
+    /* Already setup */
+    CHECK_FAIL_ERR((cx->aeadContext), SEC_ERROR_INVALID_STATE);
+
+    rv = pk11_hpke_Decap(cx, pkR, skR, enc);
+    CHECK_RV(rv);
+    rv = pk11_hpke_KeySchedule(cx, info);
+    CHECK_RV(rv);
+
+    /* Store the key context for subsequent calls to Open().
+     * PK11_CreateContextBySymKey refs the key internally. */
+    PORT_Assert(cx->key);
+    cx->aeadContext = PK11_CreateContextBySymKey(cx->aeadParams->mech,
+                                                 CKA_NSS_MESSAGE | CKA_DECRYPT,
+                                                 cx->key, &nullParams);
+    CHECK_FAIL_ERR((!cx->aeadContext), SEC_ERROR_LIBRARY_FAILURE);
+
+CLEANUP:
+    if (rv != SECSuccess) {
+        /* Clear everything past NewContext. */
+        PK11_HPKE_DestroyContext(cx, PR_FALSE);
+    }
+    return rv;
+}
+
+SECStatus
+PK11_HPKE_SetupS(HpkeContext *cx, const SECKEYPublicKey *pkE, SECKEYPrivateKey *skE,
+                 SECKEYPublicKey *pkR, const SECItem *info)
+{
+    SECStatus rv;
+    SECItem empty = { siBuffer, NULL, 0 };
+    SECKEYPublicKey *tmpPkE = NULL;
+    SECKEYPrivateKey *tmpSkE = NULL;
+    CHECK_FAIL_ERR((!cx || !pkR || !info || (!!skE != !!pkE)), SEC_ERROR_INVALID_ARGS);
+    /* Already setup */
+    CHECK_FAIL_ERR((cx->aeadContext), SEC_ERROR_INVALID_STATE);
+
+    /* If NULL was passed for the local keypair, generate one. */
+    if (skE == NULL) {
+        rv = pk11_hpke_GenerateKeyPair(cx, &tmpPkE, &tmpSkE);
+        if (rv != SECSuccess) {
+            /* Code set */
+            return SECFailure;
+        }
+        rv = pk11_hpke_Encap(cx, tmpPkE, tmpSkE, pkR);
+    } else {
+        rv = pk11_hpke_Encap(cx, pkE, skE, pkR);
+    }
+    CHECK_RV(rv);
+
+    SECItem defaultInfo = { siBuffer, NULL, 0 };
+    if (!info || !info->data) {
+        info = &defaultInfo;
+    }
+    rv = pk11_hpke_KeySchedule(cx, info);
+    CHECK_RV(rv);
+
+    PORT_Assert(cx->key);
+    cx->aeadContext = PK11_CreateContextBySymKey(cx->aeadParams->mech,
+                                                 CKA_NSS_MESSAGE | CKA_ENCRYPT,
+                                                 cx->key, &empty);
+    CHECK_FAIL_ERR((!cx->aeadContext), SEC_ERROR_LIBRARY_FAILURE);
+
+CLEANUP:
+    if (rv != SECSuccess) {
+        /* Clear everything past NewContext. */
+        PK11_HPKE_DestroyContext(cx, PR_FALSE);
+    }
+    SECKEY_DestroyPrivateKey(tmpSkE);
+    SECKEY_DestroyPublicKey(tmpPkE);
+    return rv;
+}
+
+SECStatus
+PK11_HPKE_Seal(HpkeContext *cx, const SECItem *aad, const SECItem *pt,
+               SECItem **out)
+{
+    SECStatus rv;
+    PRUint8 ivOut[12] = { 0 };
+    SECItem *ct = NULL;
+    size_t maxOut;
+    unsigned char tagBuf[HASH_LENGTH_MAX];
+    size_t tagLen;
+    unsigned int fixedBits;
+    PORT_Assert(cx->nonce->len == sizeof(ivOut));
+    memcpy(ivOut, cx->nonce->data, cx->nonce->len);
+
+    /* aad may be NULL, PT may be zero-length but not NULL. */
+    if (!cx || !cx->aeadContext ||
+        (aad && aad->len && !aad->data) ||
+        !pt || (pt->len && !pt->data) ||
+        !out) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+
+    tagLen = cx->aeadParams->tagLen;
+    maxOut = pt->len + tagLen;
+    fixedBits = (cx->nonce->len - 8) * 8;
+    ct = SECITEM_AllocItem(NULL, NULL, maxOut);
+    CHECK_FAIL(!ct);
+
+    rv = PK11_AEADOp(cx->aeadContext,
+                     CKG_GENERATE_COUNTER_XOR, fixedBits,
+                     ivOut, sizeof(ivOut),
+                     aad ? aad->data : NULL,
+                     aad ? aad->len : 0,
+                     ct->data, (int *)&ct->len, maxOut,
+                     tagBuf, tagLen,
+                     pt->data, pt->len);
+    CHECK_RV(rv);
+    CHECK_FAIL_ERR((ct->len > maxOut - tagLen), SEC_ERROR_LIBRARY_FAILURE);
+
+    /* Append the tag to the ciphertext. */
+    memcpy(&ct->data[ct->len], tagBuf, tagLen);
+    ct->len += tagLen;
+    *out = ct;
+
+CLEANUP:
+    if (rv != SECSuccess) {
+        SECITEM_ZfreeItem(ct, PR_TRUE);
+    }
+    return rv;
+}
+
+/* PKCS #11 defines the IV generator function to be ignored on
+ * decrypt (i.e. it uses the nonce input, as provided, as the IV).
+ * The sequence number is kept independently on each endpoint and
+ * the XORed IV is not transmitted, so we have to do our own IV
+ * construction XOR outside of the token. */
+static SECStatus
+pk11_hpke_makeIv(HpkeContext *cx, PRUint8 *iv, size_t ivLen)
+{
+    unsigned int counterLen = sizeof(cx->sequenceNumber);
+    PORT_Assert(cx->nonce->len == ivLen);
+    PORT_Assert(counterLen == 8);
+    if (cx->sequenceNumber == PR_UINT64(0xffffffffffffffff)) {
+        /* Overflow */
+        PORT_SetError(SEC_ERROR_INVALID_KEY);
+        return SECFailure;
+    }
+
+    memcpy(iv, cx->nonce->data, cx->nonce->len);
+    for (size_t i = 0; i < counterLen; i++) {
+        iv[cx->nonce->len - 1 - i] ^=
+            PORT_GET_BYTE_BE(cx->sequenceNumber,
+                             counterLen - 1 - i, counterLen);
+    }
+    return SECSuccess;
+}
+
+SECStatus
+PK11_HPKE_Open(HpkeContext *cx, const SECItem *aad,
+               const SECItem *ct, SECItem **out)
+{
+    SECStatus rv;
+    PRUint8 constructedNonce[12] = { 0 };
+    unsigned int tagLen;
+    SECItem *pt = NULL;
+
+    /* aad may be NULL, CT may be zero-length but not NULL. */
+    if ((!cx || !cx->aeadContext || !ct || !out) ||
+        (aad && aad->len && !aad->data) ||
+        (!ct->data || (ct->data && !ct->len))) {
+        PORT_SetError(SEC_ERROR_INVALID_ARGS);
+        return SECFailure;
+    }
+    tagLen = cx->aeadParams->tagLen;
+    CHECK_FAIL_ERR((ct->len < tagLen), SEC_ERROR_INVALID_ARGS);
+
+    pt = SECITEM_AllocItem(NULL, NULL, ct->len);
+    CHECK_FAIL(!pt);
+
+    rv = pk11_hpke_makeIv(cx, constructedNonce, sizeof(constructedNonce));
+    CHECK_RV(rv);
+
+    rv = PK11_AEADOp(cx->aeadContext, CKG_NO_GENERATE, 0,
+                     constructedNonce, sizeof(constructedNonce),
+                     aad ? aad->data : NULL,
+                     aad ? aad->len : 0,
+                     pt->data, (int *)&pt->len, pt->len,
+                     &ct->data[ct->len - tagLen], tagLen,
+                     ct->data, ct->len - tagLen);
+    CHECK_RV(rv);
+    cx->sequenceNumber++;
+    *out = pt;
+
+CLEANUP:
+    if (rv != SECSuccess) {
+        SECITEM_ZfreeItem(pt, PR_TRUE);
+    }
+    return rv;
+}
+#endif // NSS_ENABLE_DRAFT_HPKE
diff --git a/mozilla/security/nss/lib/pk11wrap/pk11hpke.h b/mozilla/security/nss/lib/pk11wrap/pk11hpke.h
new file mode 100644
index 00000000000..95a55fd336d
--- /dev/null
+++ b/mozilla/security/nss/lib/pk11wrap/pk11hpke.h
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _PK11_HPKE_H_
+#define _PK11_HPKE_H_ 1
+
+#include "blapit.h"
+#include "seccomon.h"
+
+#ifdef NSS_ENABLE_DRAFT_HPKE
+#define HPKE_DRAFT_VERSION 5
+
+#define CLEANUP                    \
+    PORT_Assert(rv == SECSuccess); \
+    cleanup
+
+/* Error code must already be set.  */
+#define CHECK_RV(rv)          \
+    if ((rv) != SECSuccess) { \
+        goto cleanup;         \
+    }
+
+/* Error code must already be set.  */
+#define CHECK_FAIL(cond) \
+    if ((cond)) {        \
+        rv = SECFailure; \
+        goto cleanup;    \
+    }
+
+#define CHECK_FAIL_ERR(cond, err) \
+    if ((cond)) {                 \
+        PORT_SetError((err));     \
+        rv = SECFailure;          \
+        goto cleanup;             \
+    }
+
+#endif /* NSS_ENABLE_DRAFT_HPKE */
+
+typedef enum {
+    HpkeModeBase = 0,
+    HpkeModePsk = 1,
+} HpkeModeId;
+
+/* https://tools.ietf.org/html/draft-irtf-cfrg-hpke-05#section-7.1 */
+typedef enum {
+    HpkeDhKemX25519Sha256 = 0x20,
+} HpkeKemId;
+
+typedef enum {
+    HpkeKdfHkdfSha256 = 1,
+} HpkeKdfId;
+
+typedef enum {
+    HpkeAeadAes128Gcm = 1,
+    HpkeAeadChaCha20Poly1305 = 3,
+} HpkeAeadId;
+
+typedef struct hpkeKemParamsStr {
+    HpkeKemId id;
+    unsigned int Nsk;
+    unsigned int Nsecret;
+    unsigned int Npk;
+    SECOidTag oidTag;
+    CK_MECHANISM_TYPE hashMech;
+} hpkeKemParams;
+
+typedef struct hpkeKdfParamsStr {
+    HpkeKdfId id;
+    unsigned int Nh;
+    CK_MECHANISM_TYPE mech;
+} hpkeKdfParams;
+
+typedef struct hpkeAeadParamsStr {
+    HpkeAeadId id;
+    unsigned int Nk;
+    unsigned int Nn;
+    unsigned int tagLen;
+    CK_MECHANISM_TYPE mech;
+} hpkeAeadParams;
+
+typedef struct HpkeContextStr HpkeContext;
+
+#endif /* _PK11_HPKE_H_ */
diff --git a/mozilla/security/nss/lib/pk11wrap/pk11pub.h b/mozilla/security/nss/lib/pk11wrap/pk11pub.h
index b7a9bdd3156..af8c0852f62 100644
--- a/mozilla/security/nss/lib/pk11wrap/pk11pub.h
+++ b/mozilla/security/nss/lib/pk11wrap/pk11pub.h
@@ -9,6 +9,7 @@
 #include "secdert.h"
 #include "keythi.h"
 #include "certt.h"
+#include "pk11hpke.h"
 #include "pkcs11t.h"
 #include "secmodt.h"
 #include "seccomon.h"
@@ -713,6 +714,36 @@ CK_BBOOL PK11_HasAttributeSet(PK11SlotInfo *slot,
                               CK_ATTRIBUTE_TYPE type,
                               PRBool haslock /* must be set to PR_FALSE */);
 
+/**********************************************************************
+ *                   Hybrid Public Key Encryption  (draft-05)
+ **********************************************************************/
+/*
+ * NOTE: All HPKE functions will fail with SEC_ERROR_INVALID_ALGORITHM
+ * unless NSS is compiled with NSS_ENABLE_DRAFT_HPKE while spec (and
+ * implementation) is in draft. The eventual RFC number is an input to
+ * the key schedule, so applications opting into this MUST be prepared for
+ * outputs to change when the implementation is updated or finalized. */
+
+/* Some of the various HPKE arguments would ideally be const, but the
+ * underlying PK11 functions take them as non-const. To avoid lying to
+ * the application with a cast, this idiosyncrasy is exposed. */
+SECStatus PK11_HPKE_ValidateParameters(HpkeKemId kemId, HpkeKdfId kdfId, HpkeAeadId aeadId);
+HpkeContext *PK11_HPKE_NewContext(HpkeKemId kemId, HpkeKdfId kdfId, HpkeAeadId aeadId,
+                                  PK11SymKey *psk, const SECItem *pskId);
+SECStatus PK11_HPKE_Deserialize(const HpkeContext *cx, const PRUint8 *enc,
+                                unsigned int encLen, SECKEYPublicKey **outPubKey);
+void PK11_HPKE_DestroyContext(HpkeContext *cx, PRBool freeit);
+const SECItem *PK11_HPKE_GetEncapPubKey(const HpkeContext *cx);
+SECStatus PK11_HPKE_ExportSecret(const HpkeContext *cx, const SECItem *info, unsigned int L,
+                                 PK11SymKey **outKey);
+SECStatus PK11_HPKE_Open(HpkeContext *cx, const SECItem *aad, const SECItem *ct, SECItem **outPt);
+SECStatus PK11_HPKE_Seal(HpkeContext *cx, const SECItem *aad, const SECItem *pt, SECItem **outCt);
+SECStatus PK11_HPKE_Serialize(const SECKEYPublicKey *pk, PRUint8 *buf, unsigned int *len, unsigned int maxLen);
+SECStatus PK11_HPKE_SetupS(HpkeContext *cx, const SECKEYPublicKey *pkE, SECKEYPrivateKey *skE,
+                           SECKEYPublicKey *pkR, const SECItem *info);
+SECStatus PK11_HPKE_SetupR(HpkeContext *cx, const SECKEYPublicKey *pkR, SECKEYPrivateKey *skR,
+                           const SECItem *enc, const SECItem *info);
+
 /**********************************************************************
  *                   Sign/Verify
  **********************************************************************/
diff --git a/mozilla/security/nss/lib/pk11wrap/pk11wrap.gyp b/mozilla/security/nss/lib/pk11wrap/pk11wrap.gyp
index 65d690f899c..eebb4ea3cba 100644
--- a/mozilla/security/nss/lib/pk11wrap/pk11wrap.gyp
+++ b/mozilla/security/nss/lib/pk11wrap/pk11wrap.gyp
@@ -37,6 +37,7 @@
           'pk11cert.c',
           'pk11cxt.c',
           'pk11err.c',
+          'pk11hpke.c',
           'pk11kea.c',
           'pk11list.c',
           'pk11load.c',
diff --git a/mozilla/security/nss/lib/ssl/ssl3con.c b/mozilla/security/nss/lib/ssl/ssl3con.c
index 7f581e79208..d6aaa7fc194 100644
--- a/mozilla/security/nss/lib/ssl/ssl3con.c
+++ b/mozilla/security/nss/lib/ssl/ssl3con.c
@@ -6621,7 +6621,11 @@ ssl_CheckServerSessionIdCorrectness(sslSocket *ss, SECItem *sidBytes)
 
     /* TLS 1.3: We sent a session ID.  The server's should match. */
     if (!IS_DTLS(ss) && (sentRealSid || sentFakeSid)) {
-        return sidMatch;
+        if (sidMatch) {
+            ss->ssl3.hs.allowCcs = PR_TRUE;
+            return PR_TRUE;
+        }
+        return PR_FALSE;
     }
 
     /* TLS 1.3 (no SID)/DTLS 1.3: The server shouldn't send a session ID. */
@@ -8640,6 +8644,7 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length)
                 errCode = PORT_GetError();
                 goto alert_loser;
             }
+            ss->ssl3.hs.allowCcs = PR_TRUE;
         }
 
         /* TLS 1.3 requires that compression include only null. */
@@ -12974,8 +12979,15 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText)
             ss->ssl3.hs.ws != idle_handshake &&
             cText->buf->len == 1 &&
             cText->buf->buf[0] == change_cipher_spec_choice) {
-            /* Ignore the CCS. */
-            return SECSuccess;
+            if (ss->ssl3.hs.allowCcs) {
+                /* Ignore the first CCS. */
+                ss->ssl3.hs.allowCcs = PR_FALSE;
+                return SECSuccess;
+            }
+
+            /* Compatibility mode is not negotiated. */
+            alert = unexpected_message;
+            PORT_SetError(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
         }
 
         if (IS_DTLS(ss) ||
diff --git a/mozilla/security/nss/lib/ssl/sslimpl.h b/mozilla/security/nss/lib/ssl/sslimpl.h
index a3f3fe2e721..9e4616bc302 100644
--- a/mozilla/security/nss/lib/ssl/sslimpl.h
+++ b/mozilla/security/nss/lib/ssl/sslimpl.h
@@ -712,6 +712,10 @@ typedef struct SSL3HandshakeStateStr {
                                            * or received. */
     PRBool receivedCcs;                   /* A server received ChangeCipherSpec
                                            * before the handshake started. */
+    PRBool allowCcs;                      /* A server allows ChangeCipherSpec
+                                           * as the middlebox compatibility mode
+                                           * is explicitly indicarted by
+                                           * legacy_session_id in TLS 1.3 ClientHello. */
     PRBool clientCertRequested;           /* True if CertificateRequest received. */
     ssl3KEADef kea_def_mutable;           /* Used to hold the writable kea_def
                                            * we use for TLS 1.3 */
diff --git a/mozilla/security/nss/lib/util/SECerrs.h b/mozilla/security/nss/lib/util/SECerrs.h
index 206fca08717..d58813e46a2 100644
--- a/mozilla/security/nss/lib/util/SECerrs.h
+++ b/mozilla/security/nss/lib/util/SECerrs.h
@@ -549,3 +549,6 @@ ER3(SEC_ERROR_LEGACY_DATABASE, (SEC_ERROR_BASE + 177),
 
 ER3(SEC_ERROR_APPLICATION_CALLBACK_ERROR, (SEC_ERROR_BASE + 178),
     "The certificate was rejected by extra checks in the application.")
+
+ER3(SEC_ERROR_INVALID_STATE, (SEC_ERROR_BASE + 179),
+    "The attempted operation is invalid for the current state.")
diff --git a/mozilla/security/nss/lib/util/secerr.h b/mozilla/security/nss/lib/util/secerr.h
index 4fe1d8e38da..44bb5ee4a65 100644
--- a/mozilla/security/nss/lib/util/secerr.h
+++ b/mozilla/security/nss/lib/util/secerr.h
@@ -210,6 +210,8 @@ typedef enum {
 
     SEC_ERROR_APPLICATION_CALLBACK_ERROR = (SEC_ERROR_BASE + 178),
 
+    SEC_ERROR_INVALID_STATE = (SEC_ERROR_BASE + 179),
+
     /* Add new error codes above here. */
     SEC_ERROR_END_OF_LIST
 } SECErrorCodes;
diff --git a/mozilla/security/nss/tests/common/cleanup.sh b/mozilla/security/nss/tests/common/cleanup.sh
index 6316eecf84c..f9c5f59619c 100755
--- a/mozilla/security/nss/tests/common/cleanup.sh
+++ b/mozilla/security/nss/tests/common/cleanup.sh
@@ -34,6 +34,8 @@ if [ -z "${CLEANUP}" -o "${CLEANUP}" = "${SCRIPTNAME}" ]; then
     echo "IOPR_HOSTADDR_LIST=${IOPR_HOSTADDR_LIST}"
     echo "PKITS_DATA=${PKITS_DATA}"
     echo "NSS_DISABLE_HW_AES=${NSS_DISABLE_HW_AES}"
+    echo "NSS_DISABLE_HW_SHA1=${NSS_DISABLE_HW_SHA1}"
+    echo "NSS_DISABLE_HW_SHA2=${NSS_DISABLE_HW_SHA2}"
     echo "NSS_DISABLE_PCLMUL=${NSS_DISABLE_PCLMUL}"
     echo "NSS_DISABLE_AVX=${NSS_DISABLE_AVX}"
     echo "NSS_DISABLE_ARM_NEON=${NSS_DISABLE_ARM_NEON}"
 
дизайн и разработка: Vladimir Lettiev aka crux © 2004-2005, Andrew Avramenko aka liks © 2007-2008
текущий майнтейнер: Michael Shigorin