summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--automation/abi-check/expected-report-libnss3.so.txt13
-rwxr-xr-xautomation/taskcluster/scripts/build_gyp.sh2
-rw-r--r--automation/taskcluster/windows/build_gyp.sh2
-rw-r--r--coreconf/config.gypi6
-rw-r--r--coreconf/config.mk4
-rw-r--r--cpputil/nss_scoped_ptrs.h5
-rw-r--r--gtests/common/testvectors/hpke-vectors.h233
-rw-r--r--gtests/pk11_gtest/manifest.mn1
-rw-r--r--gtests/pk11_gtest/pk11_gtest.gyp1
-rw-r--r--gtests/pk11_gtest/pk11_hpke_unittest.cc547
-rw-r--r--lib/nss/nss.def13
-rw-r--r--lib/pk11wrap/exports.gyp1
-rw-r--r--lib/pk11wrap/manifest.mn4
-rw-r--r--lib/pk11wrap/pk11hpke.c1085
-rw-r--r--lib/pk11wrap/pk11hpke.h84
-rw-r--r--lib/pk11wrap/pk11pub.h31
-rw-r--r--lib/pk11wrap/pk11wrap.gyp1
-rw-r--r--lib/util/SECerrs.h3
-rw-r--r--lib/util/secerr.h2
19 files changed, 2033 insertions, 5 deletions
diff --git a/automation/abi-check/expected-report-libnss3.so.txt b/automation/abi-check/expected-report-libnss3.so.txt
index 8028bf753..17ee6232c 100644
--- a/automation/abi-check/expected-report-libnss3.so.txt
+++ b/automation/abi-check/expected-report-libnss3.so.txt
@@ -1,4 +1,15 @@
-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}
diff --git a/automation/taskcluster/scripts/build_gyp.sh b/automation/taskcluster/scripts/build_gyp.sh
index e19a6362f..2cb0deb01 100755
--- a/automation/taskcluster/scripts/build_gyp.sh
+++ b/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/automation/taskcluster/windows/build_gyp.sh b/automation/taskcluster/windows/build_gyp.sh
index 1621c2571..d7072ebbf 100644
--- a/automation/taskcluster/windows/build_gyp.sh
+++ b/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/coreconf/config.gypi b/coreconf/config.gypi
index 8cae4c48d..760b51a26 100644
--- a/coreconf/config.gypi
+++ b/coreconf/config.gypi
@@ -132,6 +132,7 @@
'mozpkix_only%': 0,
'coverage%': 0,
'softfp_cflags%': '',
+ 'enable_draft_hpke%': 0,
},
'target_defaults': {
# Settings specific to targets should go here.
@@ -568,6 +569,11 @@
'NSS_DISABLE_DBM',
],
}],
+ [ 'enable_draft_hpke==1', {
+ 'defines': [
+ 'NSS_ENABLE_DRAFT_HPKE',
+ ],
+ }],
[ 'disable_libpkix==1', {
'defines': [
'NSS_DISABLE_LIBPKIX',
diff --git a/coreconf/config.mk b/coreconf/config.mk
index e0556af14..2f7b63896 100644
--- a/coreconf/config.mk
+++ b/coreconf/config.mk
@@ -195,6 +195,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/cpputil/nss_scoped_ptrs.h b/cpputil/nss_scoped_ptrs.h
index 501f9dfe8..2c57986b1 100644
--- a/cpputil/nss_scoped_ptrs.h
+++ b/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/gtests/common/testvectors/hpke-vectors.h b/gtests/common/testvectors/hpke-vectors.h
new file mode 100644
index 000000000..dd7b417b8
--- /dev/null
+++ b/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/gtests/pk11_gtest/manifest.mn b/gtests/pk11_gtest/manifest.mn
index 12a4c2c1d..9b127159f 100644
--- a/gtests/pk11_gtest/manifest.mn
+++ b/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/gtests/pk11_gtest/pk11_gtest.gyp b/gtests/pk11_gtest/pk11_gtest.gyp
index cdad1a963..17197f9e0 100644
--- a/gtests/pk11_gtest/pk11_gtest.gyp
+++ b/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/gtests/pk11_gtest/pk11_hpke_unittest.cc b/gtests/pk11_gtest/pk11_hpke_unittest.cc
new file mode 100644
index 000000000..1858e7f10
--- /dev/null
+++ b/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/lib/nss/nss.def b/lib/nss/nss.def
index 06c0d8d42..3e888f0a0 100644
--- a/lib/nss/nss.def
+++ b/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;
@@ -1189,6 +1189,17 @@ PK11_FindEncodedCertInSlot;
;+};
;+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/lib/pk11wrap/exports.gyp b/lib/pk11wrap/exports.gyp
index 365006080..5067cade8 100644
--- a/lib/pk11wrap/exports.gyp
+++ b/lib/pk11wrap/exports.gyp
@@ -13,6 +13,7 @@
{
'files': [
'pk11func.h',
+ 'pk11hpke.h',
'pk11pqg.h',
'pk11priv.h',
'pk11pub.h',
diff --git a/lib/pk11wrap/manifest.mn b/lib/pk11wrap/manifest.mn
index 385fa5e7b..8f8a387b4 100644
--- a/lib/pk11wrap/manifest.mn
+++ b/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/lib/pk11wrap/pk11hpke.c b/lib/pk11wrap/pk11hpke.c
new file mode 100644
index 000000000..7f8fe3b1b
--- /dev/null
+++ b/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/lib/pk11wrap/pk11hpke.h b/lib/pk11wrap/pk11hpke.h
new file mode 100644
index 000000000..95a55fd33
--- /dev/null
+++ b/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/lib/pk11wrap/pk11pub.h b/lib/pk11wrap/pk11pub.h
index ebd20fc2b..0cc19f29a 100644
--- a/lib/pk11wrap/pk11pub.h
+++ b/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"
@@ -715,6 +716,36 @@ CK_BBOOL PK11_HasAttributeSet(PK11SlotInfo *slot,
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/lib/pk11wrap/pk11wrap.gyp b/lib/pk11wrap/pk11wrap.gyp
index 65d690f89..eebb4ea3c 100644
--- a/lib/pk11wrap/pk11wrap.gyp
+++ b/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/lib/util/SECerrs.h b/lib/util/SECerrs.h
index 206fca087..d58813e46 100644
--- a/lib/util/SECerrs.h
+++ b/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/lib/util/secerr.h b/lib/util/secerr.h
index 4fe1d8e38..44bb5ee4a 100644
--- a/lib/util/secerr.h
+++ b/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;