From bd4ef1c9dc4dc6c89d8a1635de56d7afa0763f19 Mon Sep 17 00:00:00 2001 From: Kevin Jacobs Date: Mon, 12 Oct 2020 17:07:02 +0000 Subject: [PATCH] Bug 1631890 - Add support for Hybrid Public Key Encryption (draft-irtf-cfrg-hpke-05). r=mt This patch adds support for Hybrid Public Key Encryption (draft-irtf-cfrg-hpke-05). Because the draft number (and the eventual RFC number) is an input to the key schedule, future updates will *not* be backwards compatible in terms of key material or encryption/decryption. For this reason, a default compilation will produce stubs that simply return an "Invalid Algorithm" error. To opt into using the HPKE functionality , compile with `NSS_ENABLE_DRAFT_HPKE` defined. Once finalized, this flag will not be required to access the functions. Lastly, the `DeriveKeyPair` API is not implemented as it adds complextiy around PKCS #11 and is unnecessary for ECH. Differential Revision: https://phabricator.services.mozilla.com/D73947 --HG-- extra : moz-landing-system : lando --- .../abi-check/expected-report-libnss3.so.txt | 13 +- automation/taskcluster/scripts/build_gyp.sh | 2 +- automation/taskcluster/windows/build_gyp.sh | 2 +- coreconf/config.gypi | 6 + coreconf/config.mk | 4 + cpputil/nss_scoped_ptrs.h | 5 + gtests/common/testvectors/hpke-vectors.h | 233 ++++ gtests/pk11_gtest/manifest.mn | 1 + gtests/pk11_gtest/pk11_gtest.gyp | 1 + gtests/pk11_gtest/pk11_hpke_unittest.cc | 547 +++++++++ lib/nss/nss.def | 13 +- lib/pk11wrap/exports.gyp | 1 + lib/pk11wrap/manifest.mn | 4 +- lib/pk11wrap/pk11hpke.c | 1085 +++++++++++++++++ lib/pk11wrap/pk11hpke.h | 84 ++ lib/pk11wrap/pk11pub.h | 31 + lib/pk11wrap/pk11wrap.gyp | 1 + lib/util/SECerrs.h | 3 + lib/util/secerr.h | 2 + 19 files changed, 2033 insertions(+), 5 deletions(-) create mode 100644 gtests/common/testvectors/hpke-vectors.h create mode 100644 gtests/pk11_gtest/pk11_hpke_unittest.cc create mode 100644 lib/pk11wrap/pk11hpke.c create mode 100644 lib/pk11wrap/pk11hpke.h diff --git a/automation/abi-check/expected-report-libnss3.so.txt b/automation/abi-check/expected-report-libnss3.so.txt index 8028bf7538..17ee6232cf 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 e19a6362fc..2cb0deb016 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 1621c25710..d7072ebbf2 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 8cae4c48d8..760b51a269 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 e0556af148..2f7b63896d 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 501f9dfe87..2c57986b14 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 0000000000..dd7b417b8a --- /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 + +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 encrypt_vecs; + std::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(0), + static_cast(32), + static_cast(1), + static_cast(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(1), + static_cast(32), + static_cast(1), + static_cast(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(0), + static_cast(32), + static_cast(1), + static_cast(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(1), + static_cast(32), + static_cast(1), + static_cast(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 12a4c2c1d8..9b127159f9 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 cdad1a9636..17197f9e09 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 0000000000..1858e7f109 --- /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 +#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 { + 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 psk_bytes = hex_string_to_bytes(vec.psk); + SECItem psk_item = {siBuffer, toUcharPtr(psk_bytes.data()), + static_cast(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 &expected, SECItem *actual) { + if (!actual) { + EXPECT_TRUE(expected.empty()); + return; + } + std::vector 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 &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 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 &info) { + SECItem info_item = {siBuffer, toUcharPtr(vec_info.data()), + static_cast(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 &enc, + const std::vector &info) { + SECItem enc_item = {siBuffer, toUcharPtr(enc.data()), + static_cast(enc.size())}; + SECItem info_item = {siBuffer, toUcharPtr(vec_info.data()), + static_cast(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 &aad_vec, + std::vector &pt_vec, SECItem **out_ct) { + SECItem aad_item = {siBuffer, toUcharPtr(aad_vec.data()), + static_cast(aad_vec.size())}; + SECItem pt_item = {siBuffer, toUcharPtr(pt_vec.data()), + static_cast(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 &aad_vec, + std::vector &ct_vec, SECItem **out_pt) { + SECItem aad_item = {siBuffer, toUcharPtr(aad_vec.data()), + static_cast(aad_vec.size())}; + SECItem ct_item = {siBuffer, toUcharPtr(ct_vec.data()), + static_cast(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 context = hex_string_to_bytes(vec.ctxt); + std::vector expected = hex_string_to_bytes(vec.exported); + SECItem context_item = {siBuffer, toUcharPtr(context.data()), + static_cast(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 msg = hex_string_to_bytes(enc_vec.pt); + std::vector aad = hex_string_to_bytes(enc_vec.aad); + std::vector 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(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(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(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(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 vec_psk_id; + std::vector vec_pkcs8_e; + std::vector vec_pkcs8_r; + std::vector vec_enc; + std::vector vec_info; + std::vector vec_key; + std::vector vec_nonce; + std::vector vec_encryptions; + std::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(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(1), HpkeKdfHkdfSha256, + HpkeAeadChaCha20Poly1305, nullptr, nullptr); + EXPECT_EQ(nullptr, cx); + EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); + + cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, static_cast(2), + HpkeAeadChaCha20Poly1305, nullptr, nullptr); + EXPECT_EQ(nullptr, cx); + EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); + cx = PK11_HPKE_NewContext(HpkeDhKemX25519Sha256, HpkeKdfHkdfSha256, + static_cast(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 06c0d8d422..3e888f0a04 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 3650060804..5067cade82 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 385fa5e7b5..8f8a387b44 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 0000000000..7f8fe3b1b0 --- /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 *)¶ms, + 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, ¶msItem, + 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 *)¶ms, + 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, ¶msItem, + 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 *)¶ms, + 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, ¶msItem, 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 0000000000..95a55fd336 --- /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 ebd20fc2be..0cc19f29a1 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" @@ -714,6 +715,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/lib/pk11wrap/pk11wrap.gyp b/lib/pk11wrap/pk11wrap.gyp index 65d690f899..eebb4ea3cb 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 206fca0871..d58813e46a 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 4fe1d8e38d..44bb5ee4a6 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;