Hi all, I trust this message finds you well. Notably, HPKE has already been successfully implemented, and now your prompt attention is sought for the critical review and merging of the MR into the main branch of Nettle. As encrypted client hello(ECH) also relies on HPKE, your swift action is highly appreciated.
HPKE MR Link:https://git.lysator.liu.se/nettle/nettle/-/merge_requests/27
Thanks, Ajit
Hi,
The MR is still in a draft phase if I remember correctly. The last modifications were not yet reviewed by Niels; When would you Niels have time to look into it? I would like to finish it up.
Congratulations on implementing ClientHello in GnuTLS! [0]
[0] - https://gitlab.com/gnutls/gnutls/-/merge_requests/1748
Best regards Pócs Norbert
On Wed, Jan 31, 2024, 17:41 Ajit singh ajeetsinghchahar2@gmail.com wrote:
Hi all, I trust this message finds you well. Notably, HPKE has already been successfully implemented, and now your prompt attention is sought for the critical review and merging of the MR into the main branch of Nettle. As encrypted client hello(ECH) also relies on HPKE, your swift action is highly appreciated.
HPKE MR Link:https://git.lysator.liu.se/nettle/nettle/-/merge_requests/27
Thanks, Ajit _______________________________________________ nettle-bugs mailing list -- nettle-bugs@lists.lysator.liu.se To unsubscribe send an email to nettle-bugs-leave@lists.lysator.liu.se
Norbert Pócs norbertpocs0@gmail.com writes:
The MR is still in a draft phase if I remember correctly. The last modifications were not yet reviewed by Niels; When would you Niels have time to look into it? I would like to finish it up.
Thanks for the reminder. I take it your email from 11 May 2023 summarizes the latest round of changes? I'll try to have another look over the weekend, and provide a round feedback limited to how much time I get to spend on it. I appreciate your effort to trim it down, but it's neverheless rather complex (some 1500 lines excluding the tests).
Regards, /Niels
pi 2. 2. 2024 o 16:19 Niels Möller nisse@lysator.liu.se napísal(a):
Norbert Pócs norbertpocs0@gmail.com writes:
The MR is still in a draft phase if I remember correctly. The last modifications were not yet reviewed by Niels; When would you Niels have time to look into it? I would like to finish it up.
Thanks for the reminder. I take it your email from 11 May 2023 summarizes the latest round of changes?
Yes, that's correct.
I'll try to have another look
over the weekend, and provide a round feedback limited to how much time I get to spend on it. I appreciate your effort to trim it down, but it's neverheless rather complex (some 1500 lines excluding the tests).
I took an another look at the PR, if there is anything possible to delete without loosing the functionality, but unfortunately didn't find anything.
Regards, /Niels
-- Niels Möller. PGP key CB4962D070D77D7FCB8BA36271D8F1FF368C6677. Internet email is subject to wholesale government surveillance.
Best regards Pocs Norbert
Norbert Pócs norbertpocs0@gmail.com writes:
I took an another look at the PR, if there is anything possible to delete without loosing the functionality, but unfortunately didn't find anything.
To get a better understanding of the HPKE spec and its complexities, I've tried to implement KEM x25519-sha256 (and nothing else from the spec). Patch below.
Some notes:
1. Nettle's hkdf interface isn't that a good fit, if one wants to avoid memcpy calls to assemble the inputs. Below, I haven't used Nettle's hkdf_extract / hkdf_expand, instead doing corresponding operations directly on hmac_sha256. Unless I'm missing something, it seems a LabeledExpand function limited to at most 32 octets of output (the sha256 digest size) is sufficient for everything in hpke, except for the Export feature.
2. I don't quite like that some functions (in particular DeriveKeyPair) are defined so that it can fail (not for x25519, though). Having a success/failure indication there forces applications to have an error handling path, that it's rather difficult to test. I see no obvious way for Nettle to shield applications from that, though.
3. For those of you who have looked closer at proposed post-quantum KEM mechanisms, is the interface suitable for those too?
4. It seems that HPKE defines a very clean interface between the KEM and the rest of the message handling, with the shared_secret the only piece of data shered between KEM and the rest of the processing.
Regards, /Niels
diff --git a/Makefile.in b/Makefile.in index f027e762..eb520f7a 100644 --- a/Makefile.in +++ b/Makefile.in @@ -225,7 +225,8 @@ hogweed_SOURCES = sexp.c sexp-format.c \ ed25519-sha512.c ed25519-sha512-pubkey.c \ ed25519-sha512-sign.c ed25519-sha512-verify.c \ ed448-shake256.c ed448-shake256-pubkey.c \ - ed448-shake256-sign.c ed448-shake256-verify.c + ed448-shake256-sign.c ed448-shake256-verify.c \ + kem-x25519-sha256.c
OPT_SOURCES = fat-arm.c fat-arm64.c fat-ppc.c fat-s390x.c fat-x86_64.c mini-gmp.c
diff --git a/hpke-kem.h b/hpke-kem.h new file mode 100644 index 00000000..00b4610b --- /dev/null +++ b/hpke-kem.h @@ -0,0 +1,71 @@ +/* hpke-kem.h + + Key encapsulation mechanism, suitable for HPKE (RFC 9180). + + Copyright (C) 2024 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#ifndef NETTLE_HPKE_KEM_H_INCLUDED +#define NETTLE_HPKE_KEM_H_INCLUDED + +#include "nettle-types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Name mangling */ +#define get_kem_x25519_sha256 nettle_get_kem_x25519_sha256 + +typedef int kem_derive_keypair_func (uint8_t *public_key, uint8_t *private_key, + size_t seed_size, const uint8_t *seed); +/* Take randomness source instead? Passing seed suites deterministic tests. */ +typedef void kem_encapsulate_func (uint8_t *shared_secret, uint8_t *encapsulation, + const uint8_t *receiver_public_key, + void *random_ctx, nettle_random_func *random); +typedef void kem_decapsulate_func (uint8_t *shared_secret, const uint8_t *encapsulation, + const uint8_t *private_key); + +struct hpke_kem { + unsigned public_key_size; + unsigned private_key_size; + unsigned encapsulation_size; + unsigned shared_secret_size; + kem_derive_keypair_func *derive_keypair; + kem_encapsulate_func *encapsulate; + kem_decapsulate_func *decapsulate; +}; + +const struct hpke_kem *get_kem_x25519_sha256 (void); + +#ifdef __cplusplus +} +#endif + +#endif /* NETTLE_HPKE_KEM_H_INCLUDED */ diff --git a/kem-x25519-sha256.c b/kem-x25519-sha256.c new file mode 100644 index 00000000..186ced6c --- /dev/null +++ b/kem-x25519-sha256.c @@ -0,0 +1,170 @@ +/* kem-x25519-sha256.c + + KEM using curve25519, suitable for HPKE (RFC 9180). + + Copyright (C) 2024 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include <string.h> + +#include "curve25519.h" +#include "hpke-kem.h" +#include "hmac.h" + +static void +labeled_extract (struct hmac_sha256_ctx *ctx, + size_t salt_size, const uint8_t *salt, + size_t label_size, const uint8_t *label, + size_t secret_size, const uint8_t *secret) +{ + static const uint8_t extract_info[] = { + 'H', 'P', 'K', 'E', '-', 'v', '1', /* Fix prefix for LabeledExpand */ + 'K', 'E', 'M', 0, 0x20, /* suite_id for x25519-HKDF-SHA256 */ + }; + uint8_t prk[SHA256_DIGEST_SIZE]; + /* Don't call hkdf_extract, to avoid explicit concatenation. */ + hmac_sha256_set_key (ctx, salt_size, salt); + hmac_sha256_update (ctx, sizeof(extract_info), extract_info); + hmac_sha256_update (ctx, label_size, label); + hmac_sha256_update (ctx, secret_size, secret); + hmac_sha256_digest (ctx, SHA256_DIGEST_SIZE, prk); + + hmac_sha256_set_key (ctx, SHA256_DIGEST_SIZE, prk); +} + +/* Specialization of labeled_expand for output size of exactly + SHA256_DIGEST_SIZE. The label and info should be passed to + hmac_sha256_update between these _init and _digest calls. */ +static void +labeled_expand_32_init (struct hmac_sha256_ctx *ctx) +{ + static const uint8_t expand_info[] = { + 0, SHA256_DIGEST_SIZE, /* Expand size. */ + 'H', 'P', 'K', 'E', '-', 'v', '1', /* Fix prefix for LabeledExpand */ + 'K', 'E', 'M', 0, 0x20, /* suite_id for x25519-HKDF-SHA256 */ + }; + hmac_sha256_update (ctx, sizeof(expand_info), expand_info); +} + +static void +labeled_expand_32_digest (struct hmac_sha256_ctx *ctx, + uint8_t *dst) +{ + static const uint8_t one = 1; /* hkdf expand counter */ + hmac_sha256_update (ctx, 1, &one); + + hmac_sha256_digest (ctx, SHA256_DIGEST_SIZE, dst); +} + +static int +x25519_sha256_derive_keypair (uint8_t *public_key, uint8_t *private_key, + size_t seed_size, const uint8_t *seed) +{ + struct hmac_sha256_ctx ctx; + labeled_extract (&ctx, 0, NULL, + 7, (const uint8_t *) "dkp_prk", + seed_size, seed); + labeled_expand_32_init (&ctx); + hmac_sha256_update (&ctx, 2, (const uint8_t *) "sk"); + labeled_expand_32_digest (&ctx, private_key); + + /* Mask private key here? See RFC9180 errata. */ + curve25519_mul_g (public_key, private_key); + return 1; +} + +static void +x25519_sha256_derive_shared_secret (uint8_t *shared_secret, + const uint8_t *dh, + const uint8_t *encapsulation, + const uint8_t *public_key) +{ + struct hmac_sha256_ctx ctx; + + labeled_extract (&ctx, 0, NULL, + 7, (const uint8_t *) "eae_prk", + CURVE25519_SIZE, dh); + + labeled_expand_32_init (&ctx); + hmac_sha256_update (&ctx, 13, (const uint8_t *) "shared_secret"); /* label */ + hmac_sha256_update (&ctx, CURVE25519_SIZE, encapsulation); /* kem_context */ + hmac_sha256_update (&ctx, CURVE25519_SIZE, public_key); + labeled_expand_32_digest (&ctx, shared_secret); +} + +static void +x25519_sha256_encapsulate (uint8_t *shared_secret, uint8_t *encapsulation, + const uint8_t *receiver_public_key, + void *random_ctx, nettle_random_func *random) +{ + uint8_t private_key[CURVE25519_SIZE]; + uint8_t dh[CURVE25519_SIZE]; + + random (random_ctx, sizeof(private_key), private_key); + curve25519_mul_g (encapsulation, private_key); + + curve25519_mul (dh, private_key, receiver_public_key); + + x25519_sha256_derive_shared_secret (shared_secret, dh, encapsulation, receiver_public_key); +} + +static void +x25519_sha256_decapsulate (uint8_t *shared_secret, + const uint8_t *encapsulation, + const uint8_t *private_key) +{ + uint8_t public_key[CURVE25519_SIZE]; + uint8_t dh[CURVE25519_SIZE]; + curve25519_mul (dh, private_key, encapsulation); + curve25519_mul_g (public_key, private_key); + + x25519_sha256_derive_shared_secret (shared_secret, + dh, encapsulation, public_key); +} + +static const struct hpke_kem +kem_x25519 = { + CURVE25519_SIZE, + CURVE25519_SIZE, + CURVE25519_SIZE, + CURVE25519_SIZE, + x25519_sha256_derive_keypair, + x25519_sha256_encapsulate, + x25519_sha256_decapsulate, +}; + +const struct hpke_kem * +get_kem_x25519_sha256 (void) +{ + return &kem_x25519; +} diff --git a/testsuite/Makefile.in b/testsuite/Makefile.in index bd630524..95c89c48 100644 --- a/testsuite/Makefile.in +++ b/testsuite/Makefile.in @@ -57,7 +57,8 @@ TS_HOGWEED_SOURCES = sexp-test.c sexp-format-test.c \ eddsa-compress-test.c eddsa-sign-test.c \ eddsa-verify-test.c ed25519-test.c ed448-test.c \ gostdsa-sign-test.c gostdsa-verify-test.c \ - gostdsa-keygen-test.c gostdsa-vko-test.c + gostdsa-keygen-test.c gostdsa-vko-test.c \ + hpke-kem-test.c
TS_SOURCES = $(TS_NETTLE_SOURCES) $(TS_HOGWEED_SOURCES) CXX_SOURCES = cxx-test.cxx diff --git a/testsuite/hpke-kem-test.c b/testsuite/hpke-kem-test.c new file mode 100644 index 00000000..d09d8220 --- /dev/null +++ b/testsuite/hpke-kem-test.c @@ -0,0 +1,146 @@ +#include "testutils.h" + +#include "hpke-kem.h" + +static void +test_derive_key (const struct hpke_kem *kem, + const struct tstring *ikm, + const struct tstring *exp_pub, + const struct tstring *exp_priv) +{ + uint8_t *pub = xalloc (kem->public_key_size); + uint8_t *priv = xalloc (kem->private_key_size); + int res; + ASSERT (kem->private_key_size == exp_priv->length); + ASSERT (kem->public_key_size == exp_pub->length); + + res = kem->derive_keypair (pub, priv, ikm->length, ikm->data); + ASSERT (res == 1); + if (!MEMEQ (kem->private_key_size, priv, exp_priv->data)) + { + fprintf(stderr, "kem->derive_keypair failed private:\ngot: "); + print_hex(kem->private_key_size, priv); + fprintf(stderr, " exp: "); + tstring_print_hex(exp_priv); + FAIL(); + } + if (!MEMEQ (kem->public_key_size, pub, exp_pub->data)) + { + fprintf(stderr, "kem->derive_keypair failed public:\ngot: "); + print_hex(kem->public_key_size, pub); + fprintf(stderr, " exp: "); + tstring_print_hex(exp_pub); + FAIL(); + } + free (pub); + free (priv); +} + +static void +test_random(void *p, size_t size, uint8_t *dst) +{ + const struct tstring *s = (const struct tstring *) p; + ASSERT (size <= s->length); + memcpy (dst, s->data, size); +} + +static void +test_encapsulate(const struct hpke_kem *kem, + const struct tstring *ephemeral_priv, + const struct tstring *receiver_pub, + /* const struct tstring *info, */ + const struct tstring *exp_encapsulation, + const struct tstring *exp_shared_secret) +{ + uint8_t *encapsulation = xalloc (kem->encapsulation_size); + uint8_t *shared_secret = xalloc (kem->shared_secret_size); + ASSERT (kem->private_key_size == ephemeral_priv->length); + ASSERT (kem->public_key_size == receiver_pub->length); + ASSERT (kem->encapsulation_size == exp_encapsulation->length); + ASSERT (kem->shared_secret_size == exp_shared_secret->length); + + kem->encapsulate (shared_secret, encapsulation, + receiver_pub->data, + (void *) ephemeral_priv, test_random); + + if (!MEMEQ (kem->encapsulation_size, encapsulation, exp_encapsulation->data)) + { + fprintf(stderr, "kem->encapsulation failed, encap:\ngot: "); + print_hex(kem->encapsulation_size, encapsulation); + fprintf(stderr, " exp: "); + tstring_print_hex(exp_encapsulation); + FAIL(); + } + if (!MEMEQ (kem->shared_secret_size, shared_secret, exp_shared_secret->data)) + { + fprintf(stderr, "kem->encapsulation failed, secret:\ngot: "); + print_hex(kem->shared_secret_size, shared_secret); + fprintf(stderr, " exp: "); + tstring_print_hex(exp_shared_secret); + FAIL(); + } + free (encapsulation); + free (shared_secret); +} + +static void +test_decapsulate(const struct hpke_kem *kem, + const struct tstring *receiver_priv, + const struct tstring *encapsulation, + const struct tstring *exp_shared_secret) +{ + uint8_t *shared_secret = xalloc (kem->shared_secret_size); + ASSERT (kem->private_key_size == receiver_priv->length); + ASSERT (kem->encapsulation_size == encapsulation->length); + ASSERT (kem->shared_secret_size == exp_shared_secret->length); + + kem->decapsulate (shared_secret, encapsulation->data, + receiver_priv->data); + if (!MEMEQ (kem->shared_secret_size, shared_secret, exp_shared_secret->data)) + { + fprintf(stderr, "kem->decapsulate failed, secret:\ngot: "); + print_hex(kem->shared_secret_size, shared_secret); + fprintf(stderr, " exp: "); + tstring_print_hex(exp_shared_secret); + FAIL(); + } + free (shared_secret); +} + +void +test_main(void) +{ + /* RFC 9180 A.1.1 */ + test_derive_key (get_kem_x25519_sha256(), + SHEX("7268600d403fce431561aef583ee1613" + "527cff655c1343f29812e66706df3234"), + SHEX("37fda3567bdbd628e88668c3c8d7e97d" + "1d1253b6d4ea6d44c150f741f1bf4431"), + SHEX("52c4a758a802cd8b936eceea31443279" + "8d5baf2d7e9235dc084ab1b9cfa2f736")); + test_derive_key (get_kem_x25519_sha256(), + SHEX("6db9df30aa07dd42ee5e8181afdb977e" + "538f5e1fec8a06223f33f7013e525037"), + SHEX("3948cfe0ad1ddb695d780e59077195da" + "6c56506b027329794ab02bca80815c4d"), + SHEX("4612c550263fc8ad58375df3f557aac5" + "31d26850903e55a9f23f21d8534e8ac8")); + + test_encapsulate (get_kem_x25519_sha256(), + SHEX("52c4a758a802cd8b936eceea31443279" + "8d5baf2d7e9235dc084ab1b9cfa2f736"), + SHEX("3948cfe0ad1ddb695d780e59077195da" + "6c56506b027329794ab02bca80815c4d"), + SHEX("37fda3567bdbd628e88668c3c8d7e97d" + "1d1253b6d4ea6d44c150f741f1bf4431"), + SHEX("fe0e18c9f024ce43799ae393c7e8fe8f" + "ce9d218875e8227b0187c04e7d2ea1fc")); + + test_decapsulate (get_kem_x25519_sha256(), + SHEX("4612c550263fc8ad58375df3f557aac5" + "31d26850903e55a9f23f21d8534e8ac8"), + SHEX("37fda3567bdbd628e88668c3c8d7e97d" + "1d1253b6d4ea6d44c150f741f1bf4431"), + SHEX("fe0e18c9f024ce43799ae393c7e8fe8f" + "ce9d218875e8227b0187c04e7d2ea1fc")); +}
nettle-bugs@lists.lysator.liu.se