Browse Source

Adds support for TLS v1.3 Encrypted Client Hello (ECH) draft-ietf-tls-esni) and HPKE (Hybrid Public Key Encryption) RFC9180.

David Garske 1 year ago
parent
commit
6b6ad38e4f
15 changed files with 4086 additions and 40 deletions
  1. 36 0
      configure.ac
  2. 3 0
      examples/configs/user_settings_all.h
  3. 4 0
      src/include.am
  4. 110 0
      src/internal.c
  5. 594 0
      src/ssl.c
  6. 1003 11
      src/tls.c
  7. 515 4
      src/tls13.c
  8. 247 1
      tests/api.c
  9. 1163 0
      wolfcrypt/src/hpke.c
  10. 163 20
      wolfcrypt/test/test.c
  11. 88 3
      wolfssl/internal.h
  12. 19 1
      wolfssl/ssl.h
  13. 136 0
      wolfssl/wolfcrypt/hpke.h
  14. 1 0
      wolfssl/wolfcrypt/include.am
  15. 4 0
      wolfssl/wolfcrypt/settings.h

+ 36 - 0
configure.ac

@@ -742,6 +742,7 @@ then
     test "$enable_trusted_ca" = "" && enable_trusted_ca=yes
     test "$enable_session_ticket" = "" && enable_session_ticket=yes
     test "$enable_earlydata" = "" && enable_earlydata=yes
+    test "$enable_ech" = "" && enable_ech=yes
 
     if test "$ENABLED_32BIT" != "yes"
     then
@@ -1120,6 +1121,25 @@ AC_ARG_ENABLE([cryptonly],
 
 AS_IF([test "x$FIPS_VERSION" = "xrand"],[ENABLED_CRYPTONLY="yes"])
 
+# ECH
+AC_ARG_ENABLE([ech],
+    [AS_HELP_STRING([--enable-ech],[Enable ECH (default: disabled)])],
+    [ ENABLED_ECH=$enableval ],
+    [ ENABLED_ECH=no ]
+    )
+if test "$ENABLED_ECH" = "yes"
+then
+    AM_CFLAGS="$AM_CFLAGS -DHAVE_ECH"
+
+    test "$enable_hpke" = "" && enable_hpke=yes
+    test "$enable_ecc" = "" && enable_ecc=yes
+    test "$enable_curve25519" = "" && enable_curve25519=yes
+    test "$enable_sha256" = "" && enable_sha256=yes
+    test "$enable_tlsx" = "" && enable_tlsx=yes
+    test "$enable_sni" = "" && enable_sni=yes
+    test "$enable_tls13" = "" && enable_tls13=yes
+fi
+
 # DTLS
 # DTLS is a prereq for the options mcast, sctp, and jni. Enabling any of those
 # without DTLS will also enable DTLS.
@@ -2980,6 +3000,20 @@ then
   AM_CFLAGS="$AM_CFLAGS -DHAVE_HKDF"
 fi
 
+
+# HPKE
+AC_ARG_ENABLE([hpke],
+    [AS_HELP_STRING([--enable-hpke],[Enable HKPE support (default: disabled)])],
+    [ ENABLED_HPKE=$enableval ],
+    [ ENABLED_HPKE=no ]
+    )
+if test "$ENABLED_HPKE" = "yes"
+then
+    AM_CFLAGS="$AM_CFLAGS -DHAVE_HPKE"
+
+    test "$enable_hkdf" = "" && enable_hkdf=yes
+fi
+
 # X9.63 KDF
 AC_ARG_ENABLE([x963kdf],
     [AS_HELP_STRING([--enable-x963kdf],[Enable X9.63 KDF support (default: disabled)])],
@@ -8450,6 +8484,7 @@ AM_CONDITIONAL([BUILD_PSA],[test "x$ENABLED_PSA" = "xyes"])
 AM_CONDITIONAL([BUILD_DTLS13],[test "x$ENABLED_DTLS13" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
 AM_CONDITIONAL([BUILD_QUIC],[test "x$ENABLED_QUIC" = "xyes"])
 AM_CONDITIONAL([BUILD_DTLS_CID],[test "x$ENABLED_DTLS_CID" = "xyes"])
+AM_CONDITIONAL([BUILD_HPKE],[test "x$ENABLED_HPKE" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
 AM_CONDITIONAL([BUILD_DTLS],[test "x$ENABLED_DTLS" = "xyes" || test "x$ENABLED_USERSETTINGS" = "xyes"])
 AM_CONDITIONAL([BUILD_MAXQ10XX],[test "x$ENABLED_MAXQ10XX" = "xyes"])
 
@@ -8740,6 +8775,7 @@ echo "   * PWDBASED:                   $ENABLED_PWDBASED"
 echo "   * scrypt:                     $ENABLED_SCRYPT"
 echo "   * wolfCrypt Only:             $ENABLED_CRYPTONLY"
 echo "   * HKDF:                       $ENABLED_HKDF"
+echo "   * HPKE:                       $ENABLED_HPKE"
 echo "   * X9.63 KDF:                  $ENABLED_X963KDF"
 echo "   * MD4:                        $ENABLED_MD4"
 echo "   * PSK:                        $ENABLED_PSK"

+ 3 - 0
examples/configs/user_settings_all.h

@@ -234,6 +234,9 @@ extern "C" {
 #define HAVE_KEYING_MATERIAL
 #define WOLFSSL_HAVE_PRF
 
+/* Encrypted Client Hello */
+#define HAVE_HPKE
+#define HAVE_ECH
 
 /* Non-Standard Algorithms (DG disabled) */
 //#define HAVE_CAMELLIA

+ 4 - 0
src/include.am

@@ -509,6 +509,10 @@ if BUILD_ASN
 src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/asn.c
 endif
 
+if BUILD_HPKE
+src_libwolfssl@LIBSUFFIX@_la_SOURCES += wolfcrypt/src/hpke.c
+endif
+
 endif !BUILD_FIPS_RAND
 
 if BUILD_CODING

+ 110 - 0
src/internal.c

@@ -2398,6 +2398,36 @@ void wolfSSL_CRYPTO_cleanup_ex_data(WOLFSSL_CRYPTO_EX_DATA* ex_data)
 }
 #endif /* HAVE_EX_DATA_CLEANUP_HOOKS */
 
+#if defined(HAVE_ECH)
+/* free all ech configs in the list */
+static void FreeEchConfigs(WOLFSSL_EchConfig* configs, void* heap)
+{
+    WOLFSSL_EchConfig* working_config = configs;
+    WOLFSSL_EchConfig* next_config;
+
+    while (working_config != NULL) {
+        next_config = working_config->next;
+
+        XFREE(working_config->cipherSuites, heap, DYNAMIC_TYPE_TMP_BUFFER);
+        XFREE(working_config->publicName, heap, DYNAMIC_TYPE_TMP_BUFFER);
+
+        if (working_config->raw != NULL)
+            XFREE(working_config->raw, heap, DYNAMIC_TYPE_TMP_BUFFER);
+
+        if (working_config->receiverPrivkey != NULL) {
+            wc_HpkeFreeKey(NULL, working_config->kemId,
+                working_config->receiverPrivkey, heap);
+        }
+
+        XFREE(working_config, heap, DYNAMIC_TYPE_TMP_BUFFER);
+
+        working_config = next_config;
+    }
+
+    (void)heap;
+}
+#endif
+
 /* In case contexts are held in array and don't want to free actual ctx. */
 
 /* The allocations done in InitSSL_Ctx must be free'd with ctx->onHeapHint
@@ -2554,6 +2584,10 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx)
         ctx->staticKELockInit = 0;
     }
     #endif
+#endif
+#if defined(HAVE_ECH)
+    FreeEchConfigs(ctx->echConfigs, ctx->heap);
+    ctx->echConfigs = NULL;
 #endif
     (void)heapAtCTXInit;
 }
@@ -6479,6 +6513,71 @@ void FreeHandshakeHashes(WOLFSSL* ssl)
     }
 }
 
+/* copy the hashes from source to a newly made destination return status */
+int InitHandshakeHashesAndCopy(WOLFSSL* ssl, HS_Hashes* source,
+  HS_Hashes** destination)
+{
+    int ret = 0;
+    HS_Hashes* tmpHashes;
+
+    if (source == NULL)
+        return BAD_FUNC_ARG;
+
+    /* save the original so we can put it back afterward */
+    tmpHashes = ssl->hsHashes;
+    ssl->hsHashes = NULL;
+
+    InitHandshakeHashes(ssl);
+
+    *destination = ssl->hsHashes;
+    ssl->hsHashes = tmpHashes;
+
+  /* now copy the source contents to the destination */
+#ifndef NO_OLD_TLS
+    #ifndef NO_SHA
+        ret = wc_ShaCopy(&source->hashSha, &(*destination)->hashSha);
+    #endif
+    #ifndef NO_MD5
+        if (ret == 0)
+            ret = wc_Md5Copy(&source->hashMd5, &(*destination)->hashMd5);
+    #endif
+    #endif /* !NO_OLD_TLS */
+    #ifndef NO_SHA256
+        if (ret == 0)
+            ret = wc_Sha256Copy(&source->hashSha256,
+                &(*destination)->hashSha256);
+    #endif
+    #ifdef WOLFSSL_SHA384
+        if (ret == 0)
+            ret = wc_Sha384Copy(&source->hashSha384,
+                &(*destination)->hashSha384);
+    #endif
+    #ifdef WOLFSSL_SHA512
+        if (ret == 0)
+            ret = wc_Sha512Copy(&source->hashSha512,
+                &(*destination)->hashSha512);
+    #endif
+    #if (defined(HAVE_ED25519) || defined(HAVE_ED448)) && \
+                                              !defined(WOLFSSL_NO_CLIENT_AUTH)
+        if (ret == 0 && source->messages != NULL) {
+            (*destination)->messages = (byte*)XMALLOC(source->length, ssl->heap,
+                DYNAMIC_TYPE_HASHES);
+            (*destination)->length = source->length;
+            (*destination)->prevLen = source->prevLen;
+
+            if ((*destination)->messages == NULL) {
+                ret = MEMORY_E;
+            }
+            else {
+                XMEMCPY((*destination)->messages, source->messages,
+                    source->length);
+            }
+        }
+    #endif
+
+    return ret;
+}
+
 /* called if user attempts to re-use WOLFSSL object for a new session.
  * For example wolfSSL_clear() is called then wolfSSL_connect or accept */
 int ReinitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup)
@@ -7463,6 +7562,17 @@ void SSL_ResourceFree(WOLFSSL* ssl)
         ForceZero(&ssl->clientSecret, sizeof(ssl->clientSecret));
         ForceZero(&ssl->serverSecret, sizeof(ssl->serverSecret));
     }
+
+#if defined(HAVE_ECH)
+    if (ssl->options.useEch == 1) {
+        FreeEchConfigs(ssl->echConfigs, ssl->heap);
+        ssl->echConfigs = NULL;
+        /* free the ech specific hashes */
+        ssl->hsHashes = ssl->hsHashesEch;
+        FreeHandshakeHashes(ssl);
+        ssl->options.useEch = 0;
+    }
+#endif
 #endif
 #ifdef WOLFSSL_HAVE_TLS_UNIQUE
     ForceZero(&ssl->clientFinished, TLS_FINISHED_SZ_MAX);

+ 594 - 0
src/ssl.c

@@ -222,6 +222,8 @@
     #endif /* !WOLFSSL_NO_OPENSSL_RAND_CB */
 #endif /* OPENSSL_EXTRA */
 
+#include <wolfssl/wolfcrypt/hpke.h>
+
 #if defined(OPENSSL_EXTRA) && defined(HAVE_ECC)
 const WOLF_EC_NIST_NAME kNistCurves[] = {
     {XSTR_SIZEOF("P-192"),   "P-192",   NID_X9_62_prime192v1},
@@ -260,6 +262,598 @@ const WOLF_EC_NIST_NAME kNistCurves[] = {
 };
 #endif
 
+#if defined(HAVE_ECH)
+/* create the hpke key and ech config to send to clients */
+int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName,
+    word16 kemId, word16 kdfId, word16 aeadId)
+{
+    int ret = 0;
+    word16 encLen = DHKEM_X25519_ENC_LEN;
+#ifdef WOLFSSL_SMALL_STACK
+    Hpke* hpke = NULL;
+    WC_RNG* rng;
+#else
+    Hpke hpke[1];
+    WC_RNG rng[1];
+#endif
+
+    if (ctx == NULL || publicName == NULL)
+        return BAD_FUNC_ARG;
+
+#ifdef WOLFSSL_SMALL_STACK
+    rng = (WC_RNG*)XMALLOC(sizeof(WC_RNG), ctx->heap, DYNAMIC_TYPE_RNG);
+    if (rng == NULL)
+        return MEMORY_E;
+#endif
+    ret = wc_InitRng(rng);
+    if (ret != 0) {
+    #ifdef WOLFSSL_SMALL_STACK
+        XFREE(rng, ctx->heap, DYNAMIC_TYPE_RNG);
+    #endif
+        return ret;
+    }
+
+    ctx->echConfigs = (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig),
+        ctx->heap, DYNAMIC_TYPE_TMP_BUFFER);
+    if (ctx->echConfigs == NULL)
+        ret = MEMORY_E;
+    else
+        XMEMSET(ctx->echConfigs, 0, sizeof(WOLFSSL_EchConfig));
+
+    /* set random config id */
+    if (ret == 0)
+        ret = wc_RNG_GenerateByte(rng, &ctx->echConfigs->configId);
+
+    /* if 0 is selected for algorithms use default, may change with draft */
+    if (kemId == 0)
+        kemId = DHKEM_X25519_HKDF_SHA256;
+
+    if (kdfId == 0)
+        kdfId = HKDF_SHA256;
+
+    if (aeadId == 0)
+        aeadId = HPKE_AES_128_GCM;
+
+    if (ret == 0) {
+        /* set the kem id */
+        ctx->echConfigs->kemId = kemId;
+
+        /* set the cipher suite, only 1 for now */
+        ctx->echConfigs->numCipherSuites = 1;
+        ctx->echConfigs->cipherSuites = (EchCipherSuite*)XMALLOC(
+            sizeof(EchCipherSuite), ctx->heap, DYNAMIC_TYPE_TMP_BUFFER);
+
+        if (ctx->echConfigs->cipherSuites == NULL) {
+            ret = MEMORY_E;
+        }
+        else {
+            ctx->echConfigs->cipherSuites[0].kdfId = kdfId;
+            ctx->echConfigs->cipherSuites[0].aeadId = aeadId;
+        }
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    if (ret == 0) {
+        hpke = (Hpke*)XMALLOC(sizeof(Hpke), ctx->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        if (hpke == NULL)
+            ret = MEMORY_E;
+    }
+#endif
+
+    if (ret == 0)
+        ret = wc_HpkeInit(hpke, kemId, kdfId, aeadId, ctx->heap);
+
+    /* generate the receiver private key */
+    if (ret == 0)
+        ret = wc_HpkeGenerateKeyPair(hpke, &ctx->echConfigs->receiverPrivkey,
+            rng);
+
+    /* done with RNG */
+    wc_FreeRng(rng);
+
+    /* serialize the receiver key */
+    if (ret == 0)
+        ret = wc_HpkeSerializePublicKey(hpke, ctx->echConfigs->receiverPrivkey,
+            ctx->echConfigs->receiverPubkey, &encLen);
+
+    if (ret == 0) {
+        ctx->echConfigs->publicName = (char*)XMALLOC(XSTRLEN(publicName) + 1,
+            ctx->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        if (ctx->echConfigs->publicName == NULL) {
+            ret = MEMORY_E;
+        }
+        else {
+            XMEMCPY(ctx->echConfigs->publicName, publicName,
+                XSTRLEN(publicName) + 1);
+        }
+    }
+
+    if (ret != 0) {
+        if (ctx->echConfigs) {
+            XFREE(ctx->echConfigs->cipherSuites, ctx->heap,
+                DYNAMIC_TYPE_TMP_BUFFER);
+            XFREE(ctx->echConfigs->publicName, ctx->heap,
+                DYNAMIC_TYPE_TMP_BUFFER);
+            XFREE(ctx->echConfigs, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER);
+            /* set to null to avoid double free in cleanup */
+            ctx->echConfigs = NULL;
+        }
+    }
+
+    if (ret == 0)
+        ret = WOLFSSL_SUCCESS;
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(hpke, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER);
+    XFREE(rng, ctx->heap, DYNAMIC_TYPE_RNG);
+#endif
+
+    return ret;
+}
+
+/* get the ech configs that the server context is using */
+int wolfSSL_CTX_GetEchConfigs(WOLFSSL_CTX* ctx, byte* output,
+    word32* outputLen) {
+    if (ctx == NULL || outputLen == NULL)
+        return BAD_FUNC_ARG;
+
+    /* if we don't have ech configs */
+    if (ctx->echConfigs == NULL) {
+        return WOLFSSL_FATAL_ERROR;
+    }
+
+    return GetEchConfigsEx(ctx->echConfigs, output, outputLen);
+}
+
+/* set the ech config from base64 for our client ssl object, base64 is the
+ * format ech configs are sent using dns records */
+int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64,
+    word32 echConfigs64Len)
+{
+    int ret = 0;
+    word32 decodedLen = echConfigs64Len * 3 / 4 + 1;
+    byte* decodedConfigs;
+
+    if (ssl == NULL || echConfigs64 == NULL || echConfigs64Len == 0)
+        return BAD_FUNC_ARG;
+
+    /* already have ech configs */
+    if (ssl->options.useEch == 1) {
+        return WOLFSSL_FATAL_ERROR;
+    }
+
+    decodedConfigs = (byte*)XMALLOC(decodedLen, ssl->heap,
+        DYNAMIC_TYPE_TMP_BUFFER);
+
+    if (decodedConfigs == NULL)
+        return MEMORY_E;
+
+    decodedConfigs[decodedLen - 1] = 0;
+
+    /* decode the echConfigs */
+    ret = Base64_Decode((byte*)echConfigs64, echConfigs64Len,
+      decodedConfigs, &decodedLen);
+
+    if (ret != 0) {
+        XFREE(decodedConfigs, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        return ret;
+    }
+
+    ret = wolfSSL_SetEchConfigs(ssl, decodedConfigs, decodedLen);
+
+    XFREE(decodedConfigs, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+
+    return ret;
+}
+
+/* set the ech config from a raw buffer, this is the format ech configs are
+ * sent using retry_configs from the ech server */
+int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs,
+  word32 echConfigsLen)
+{
+    int ret = 0;
+    int i;
+    int j;
+    word16 totalLength;
+    word16 version;
+    word16 length;
+    word16 hpkePubkeyLen;
+    word16 cipherSuitesLen;
+    word16 publicNameLen;
+    WOLFSSL_EchConfig* configList = NULL;
+    WOLFSSL_EchConfig* workingConfig = NULL;
+    WOLFSSL_EchConfig* lastConfig = NULL;
+    byte* echConfig = NULL;
+
+    if (ssl == NULL || echConfigs == NULL || echConfigsLen == 0)
+        return BAD_FUNC_ARG;
+
+    /* already have ech configs */
+    if (ssl->options.useEch == 1) {
+        return WOLFSSL_FATAL_ERROR;
+    }
+
+    /* check that the total length is well formed */
+    ato16(echConfigs, &totalLength);
+
+    if (totalLength != echConfigsLen - 2) {
+        return WOLFSSL_FATAL_ERROR;
+    }
+
+    /* skip the total length uint16_t */
+    i = 2;
+
+    do {
+        echConfig = (byte*)echConfigs + i;
+        ato16(echConfig, &version);
+        ato16(echConfig + 2, &length);
+
+        /* if the version does not match */
+        if (version != TLSX_ECH) {
+            /* we hit the end of the configs */
+            if ( (word32)i + 2 >= echConfigsLen ) {
+                break;
+            }
+
+            /* skip this config, +4 for version and length */
+            i += length + 4;
+            continue;
+        }
+
+        /* check if the length will overrun the buffer */
+        if ((word32)i + length + 4 > echConfigsLen) {
+            break;
+        }
+
+        if (workingConfig == NULL) {
+            workingConfig =
+                (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig),
+                ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+            configList = workingConfig;
+            workingConfig->next = NULL;
+        }
+        else {
+            lastConfig = workingConfig;
+            workingConfig->next =
+                (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig),
+                ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+            workingConfig = workingConfig->next;
+        }
+
+        if (workingConfig == NULL) {
+            ret = MEMORY_E;
+            break;
+        }
+
+        XMEMSET(workingConfig, 0, sizeof(WOLFSSL_EchConfig));
+
+        /* rawLen */
+        workingConfig->rawLen = length + 4;
+
+        /* raw body */
+        workingConfig->raw = (byte*)XMALLOC(workingConfig->rawLen,
+            ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        if (workingConfig->raw == NULL) {
+            ret = MEMORY_E;
+            break;
+        }
+
+        XMEMCPY(workingConfig->raw, echConfig, workingConfig->rawLen);
+
+        /* skip over version and length */
+        echConfig += 4;
+
+        /* configId, 1 byte */
+        workingConfig->configId = *(echConfig);
+        echConfig++;
+        /* kemId, 2 bytes */
+        ato16(echConfig, &workingConfig->kemId);
+        echConfig += 2;
+        /* hpke public_key length, 2 bytes */
+        ato16(echConfig, &hpkePubkeyLen);
+        echConfig += 2;
+        /* hpke public_key */
+        XMEMCPY(workingConfig->receiverPubkey, echConfig, hpkePubkeyLen);
+        echConfig += hpkePubkeyLen;
+        /* cipherSuitesLen */
+        ato16(echConfig, &cipherSuitesLen);
+
+        workingConfig->cipherSuites = (EchCipherSuite*)XMALLOC(cipherSuitesLen,
+            ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        if (workingConfig->cipherSuites == NULL) {
+            ret = MEMORY_E;
+            break;
+        }
+
+        echConfig += 2;
+        workingConfig->numCipherSuites = cipherSuitesLen / 4;
+        /* cipherSuites */
+        for (j = 0; j < workingConfig->numCipherSuites; j++) {
+            ato16(echConfig + j * 4, &workingConfig->cipherSuites[j].kdfId);
+            ato16(echConfig + j * 4 + 2,
+                &workingConfig->cipherSuites[j].aeadId);
+        }
+        echConfig += cipherSuitesLen;
+        /* publicNameLen */
+        ato16(echConfig, &publicNameLen);
+        workingConfig->publicName = (char*)XMALLOC(publicNameLen + 1,
+            ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        if (workingConfig->publicName == NULL) {
+            ret = MEMORY_E;
+            break;
+        }
+
+        echConfig += 2;
+        /* publicName */
+        XMEMCPY(workingConfig->publicName, echConfig, publicNameLen);
+        /* null terminated */
+        workingConfig->publicName[publicNameLen] = 0;
+
+        /* add length to go to next config, +4 for version and length */
+        i += length + 4;
+
+        /* check that we support this config */
+        for (j = 0; j < HPKE_SUPPORTED_KEM_LEN; j++) {
+            if (hpkeSupportedKem[j] == workingConfig->kemId)
+                break;
+        }
+
+        /* if we don't support the kem or at least one cipher suite */
+        if (j >= HPKE_SUPPORTED_KEM_LEN ||
+            EchConfigGetSupportedCipherSuite(workingConfig) < 0)
+        {
+            XFREE(workingConfig->cipherSuites, ssl->heap,
+                DYNAMIC_TYPE_TMP_BUFFER);
+            XFREE(workingConfig->publicName, ssl->heap,
+                DYNAMIC_TYPE_TMP_BUFFER);
+            XFREE(workingConfig->raw, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+            workingConfig = lastConfig;
+        }
+    } while ((word32)i < echConfigsLen);
+
+    /* if we found valid configs */
+    if (ret == 0 && configList != NULL) {
+        ssl->options.useEch = 1;
+        ssl->echConfigs = configList;
+
+        return WOLFSSL_SUCCESS;
+    }
+
+    workingConfig = configList;
+
+    while (workingConfig != NULL) {
+        lastConfig = workingConfig;
+        workingConfig = workingConfig->next;
+
+        XFREE(lastConfig->cipherSuites, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        XFREE(lastConfig->publicName, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        XFREE(lastConfig->raw, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+
+        XFREE(lastConfig, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+    }
+
+    if (ret == 0)
+        return WOLFSSL_FATAL_ERROR;
+
+    return ret;
+}
+
+/* get the raw ech config from our struct */
+int GetEchConfig(WOLFSSL_EchConfig* config, byte* output, word32* outputLen)
+{
+    int i;
+    word16 totalLen = 0;
+
+    if (config == NULL || (output == NULL && outputLen == NULL))
+        return BAD_FUNC_ARG;
+
+    /* 2 for version */
+    totalLen += 2;
+    /* 2 for length */
+    totalLen += 2;
+    /* 1 for configId */
+    totalLen += 1;
+    /* 2 for kemId */
+    totalLen += 2;
+    /* 2 for hpke_len */
+    totalLen += 2;
+
+    /* hpke_pub_key */
+    switch (config->kemId) {
+        case DHKEM_P256_HKDF_SHA256:
+            totalLen += DHKEM_P256_ENC_LEN;
+            break;
+        case DHKEM_P384_HKDF_SHA384:
+            totalLen += DHKEM_P384_ENC_LEN;
+            break;
+        case DHKEM_P521_HKDF_SHA512:
+            totalLen += DHKEM_P521_ENC_LEN;
+            break;
+        case DHKEM_X25519_HKDF_SHA256:
+            totalLen += DHKEM_X25519_ENC_LEN;
+            break;
+        case DHKEM_X448_HKDF_SHA512:
+            totalLen += DHKEM_X448_ENC_LEN;
+            break;
+    }
+
+    /* cipherSuitesLen */
+    totalLen += 2;
+    /* cipherSuites */
+    totalLen += config->numCipherSuites * 4;
+    /* public name len */
+    totalLen += 2;
+
+    /* public name */
+    totalLen += XSTRLEN(config->publicName);
+    /* trailing zeros */
+    totalLen += 2;
+
+    if (output == NULL) {
+        *outputLen = totalLen;
+        return LENGTH_ONLY_E;
+    }
+
+    if (totalLen > *outputLen) {
+        *outputLen = totalLen;
+        return INPUT_SIZE_E;
+    }
+
+    /* version */
+    c16toa(TLSX_ECH, output);
+    output += 2;
+
+    /* length - 4 for version and length itself */
+    c16toa(totalLen - 4, output);
+    output += 2;
+
+    /* configId */
+    *output = config->configId;
+    output++;
+    /* kemId */
+    c16toa(config->kemId, output);
+    output += 2;
+
+    /* length and key itself */
+    switch (config->kemId) {
+        case DHKEM_P256_HKDF_SHA256:
+            c16toa(DHKEM_P256_ENC_LEN, output);
+            output += 2;
+            XMEMCPY(output, config->receiverPubkey, DHKEM_P256_ENC_LEN);
+            output += DHKEM_P256_ENC_LEN;
+            break;
+        case DHKEM_P384_HKDF_SHA384:
+            c16toa(DHKEM_P384_ENC_LEN, output);
+            output += 2;
+            XMEMCPY(output, config->receiverPubkey, DHKEM_P384_ENC_LEN);
+            output += DHKEM_P384_ENC_LEN;
+            break;
+        case DHKEM_P521_HKDF_SHA512:
+            c16toa(DHKEM_P521_ENC_LEN, output);
+            output += 2;
+            XMEMCPY(output, config->receiverPubkey, DHKEM_P521_ENC_LEN);
+            output += DHKEM_P521_ENC_LEN;
+            break;
+        case DHKEM_X25519_HKDF_SHA256:
+            c16toa(DHKEM_X25519_ENC_LEN, output);
+            output += 2;
+            XMEMCPY(output, config->receiverPubkey, DHKEM_X25519_ENC_LEN);
+            output += DHKEM_X25519_ENC_LEN;
+            break;
+        case DHKEM_X448_HKDF_SHA512:
+            c16toa(DHKEM_X448_ENC_LEN, output);
+            output += 2;
+            XMEMCPY(output, config->receiverPubkey, DHKEM_X448_ENC_LEN);
+            output += DHKEM_X448_ENC_LEN;
+            break;
+    }
+
+    /* cipherSuites len */
+    c16toa(config->numCipherSuites * 4, output);
+    output += 2;
+
+    /* cipherSuites */
+    for (i = 0; i < config->numCipherSuites; i++) {
+        c16toa(config->cipherSuites[i].kdfId, output);
+        output += 2;
+        c16toa(config->cipherSuites[i].aeadId, output);
+        output += 2;
+    }
+
+    /* publicName len */
+    c16toa(XSTRLEN(config->publicName), output);
+    output += 2;
+
+    /* publicName */
+    XMEMCPY(output, config->publicName,
+        XSTRLEN(config->publicName));
+    output += XSTRLEN(config->publicName);
+
+    /* terminating zeros */
+    c16toa(0, output);
+    /* output += 2; */
+
+    *outputLen = totalLen;
+
+    return 0;
+}
+
+/* wrapper function to get ech configs from application code */
+int wolfSSL_GetEchConfigs(WOLFSSL* ssl, byte* output, word32* outputLen)
+{
+    if (ssl == NULL || outputLen == NULL)
+        return BAD_FUNC_ARG;
+
+    /* if we don't have ech configs */
+    if (ssl->options.useEch != 1) {
+        return WOLFSSL_FATAL_ERROR;
+    }
+
+    return GetEchConfigsEx(ssl->echConfigs, output, outputLen);
+}
+
+/* get the raw ech configs from our linked list of ech config structs */
+int GetEchConfigsEx(WOLFSSL_EchConfig* configs, byte* output, word32* outputLen)
+{
+    int ret = 0;
+    WOLFSSL_EchConfig* workingConfig = NULL;
+    byte* outputStart = output;
+    word32 totalLen = 2;
+    word32 workingOutputLen;
+
+    if (configs == NULL || outputLen == NULL)
+        return BAD_FUNC_ARG;
+
+    workingOutputLen = *outputLen - totalLen;
+
+    /* skip over total length which we fill in later */
+    if (output != NULL)
+        output += 2;
+
+    workingConfig = configs;
+
+    while (workingConfig != NULL) {
+        /* get this config */
+        ret = GetEchConfig(workingConfig, output, &workingOutputLen);
+
+        if (output != NULL)
+            output += workingOutputLen;
+
+        /* add this config's length to the total length */
+        totalLen += workingOutputLen;
+
+        if (totalLen > *outputLen)
+            workingOutputLen = 0;
+        else
+            workingOutputLen = *outputLen - totalLen;
+
+        /* only error we break on, other 2 we need to keep finding length */
+        if (ret == BAD_FUNC_ARG)
+            return BAD_FUNC_ARG;
+
+        workingConfig = workingConfig->next;
+    }
+
+    if (output == NULL) {
+        *outputLen = totalLen;
+        return LENGTH_ONLY_E;
+    }
+
+    if (totalLen > *outputLen) {
+        *outputLen = totalLen;
+        return INPUT_SIZE_E;
+    }
+
+    /* total size -2 for size itself */
+    c16toa(totalLen - 2, outputStart);
+
+    *outputLen = totalLen;
+
+    return WOLFSSL_SUCCESS;
+}
+#endif /* HAVE_ECH */
+
+
 #if defined(WOLFSSL_RENESAS_TSIP_TLS) || defined(WOLFSSL_RENESAS_SCEPROTECT)
 #include <wolfssl/wolfcrypt/port/Renesas/renesas_cmn.h>
 #endif

File diff suppressed because it is too large
+ 1003 - 11
src/tls.c


+ 515 - 4
src/tls13.c

@@ -165,6 +165,23 @@ static const byte tls13ProtocolLabel[TLS13_PROTOCOL_LABEL_SZ + 1] = "tls13 ";
 static const byte dtls13ProtocolLabel[DTLS13_PROTOCOL_LABEL_SZ + 1] = "dtls13";
 #endif /* WOLFSSL_DTLS13 */
 
+#if defined(HAVE_ECH)
+#define ECH_ACCEPT_CONFIRMATION_SZ 8
+#define ECH_ACCEPT_CONFIRMATION_LABEL_SZ 23
+static const byte
+    echAcceptConfirmationLabel[ECH_ACCEPT_CONFIRMATION_LABEL_SZ + 1] =
+    "ech accept confirmation";
+#endif
+
+#ifndef NO_CERTS
+#if !defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \
+    defined(HAVE_ED448) || defined(HAVE_PQC)
+
+static WC_INLINE int GetMsgHash(WOLFSSL* ssl, byte* hash);
+
+#endif
+#endif
+
 /* Expand data using HMAC, salt and label and info.
  * TLS v1.3 defines this function. Use callback if available.
  *
@@ -253,7 +270,6 @@ PRAGMA_GCC_DIAG_POP;
 }
 #endif /* !HAVE_FIPS || !wc_Tls13_HKDF_Expand_Label */
 
-
 /* Derive a key from a message.
  *
  * ssl        The SSL/TLS object.
@@ -3829,6 +3845,71 @@ static int WritePSKBinders(WOLFSSL* ssl, byte* output, word32 idx)
 }
 #endif
 
+#if defined(HAVE_ECH)
+/* returns the index of the first supported cipher suite, -1 if none */
+int EchConfigGetSupportedCipherSuite(WOLFSSL_EchConfig* config)
+{
+    int i, j, supported = 0;
+
+    for (i = 0; i < config->numCipherSuites; i++) {
+        supported = 0;
+
+        for (j = 0; j < HPKE_SUPPORTED_KDF_LEN; j++) {
+            if (config->cipherSuites[i].kdfId == hpkeSupportedKdf[j])
+                break;
+        }
+
+        if (j < HPKE_SUPPORTED_KDF_LEN)
+            for (j = 0; j < HPKE_SUPPORTED_AEAD_LEN; j++) {
+                if (config->cipherSuites[i].aeadId == hpkeSupportedAead[j]) {
+                    supported = 1;
+                    break;
+                }
+            }
+
+        if (supported)
+            return i;
+    }
+
+    return -1;
+}
+
+/* returns status after we hash the ech inner */
+static int EchHashHelloInner(WOLFSSL* ssl, WOLFSSL_ECH* ech)
+{
+    int ret;
+    HS_Hashes* tmpHashes;
+    byte falseHeader[HANDSHAKE_HEADER_SZ];
+
+    if (ssl == NULL || ech == NULL)
+        return BAD_FUNC_ARG;
+
+    /* switch hsHashes to the ech version */
+    InitHandshakeHashesAndCopy(ssl, ssl->hsHashes, &ssl->hsHashesEch);
+
+    /* swap hsHashes so the regular hash functions work */
+    tmpHashes = ssl->hsHashes;
+    ssl->hsHashes = ssl->hsHashesEch;
+
+    /* do the handshake header then the body */
+    AddTls13HandShakeHeader(falseHeader,
+        ech->innerClientHelloLen - ech->paddingLen - ech->hpke->Nt, 0, 0,
+        client_hello, ssl);
+    ret = HashRaw(ssl, falseHeader, HANDSHAKE_HEADER_SZ);
+
+    /* hash the body */
+    if (ret == 0) {
+        ret = HashRaw(ssl, ech->innerClientHello,
+              ech->innerClientHelloLen - ech->paddingLen - ech->hpke->Nt);
+    }
+
+    /* swap hsHashes back */
+    ssl->hsHashes = tmpHashes;
+
+    return ret;
+}
+#endif
+
 /* handle generation of TLS 1.3 client_hello (1) */
 /* Send a ClientHello message to the server.
  * Include the information required to start a handshake with servers using
@@ -3844,6 +3925,11 @@ typedef struct Sch13Args {
     word32 idx;
     int    sendSz;
     word16 length;
+#if defined(HAVE_ECH)
+    int clientRandomOffset;
+    int preXLength;
+    WOLFSSL_ECH* ech;
+#endif
 } Sch13Args;
 
 int SendTls13ClientHello(WOLFSSL* ssl)
@@ -3857,7 +3943,6 @@ int SendTls13ClientHello(WOLFSSL* ssl)
 #endif
     byte major, tls12minor;
 
-
     WOLFSSL_START(WC_FUNC_CLIENT_HELLO_SEND);
     WOLFSSL_ENTER("SendTls13ClientHello");
 
@@ -4015,6 +4100,38 @@ int SendTls13ClientHello(WOLFSSL* ssl)
             return ret;
     }
 #endif
+
+    /* find length of outer and inner */
+#if defined(HAVE_ECH)
+    if (ssl->options.useEch == 1) {
+        TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH);
+        if (echX == NULL)
+            return -1;
+
+        args->ech = (WOLFSSL_ECH*)echX->data;
+        if (args->ech == NULL)
+            return -1;
+
+        /* set the type to inner */
+        args->ech->type = ECH_TYPE_INNER;
+        args->preXLength = args->length;
+
+        /* get size for inner */
+        ret = TLSX_GetRequestSize(ssl, client_hello, &args->length);
+        if (ret != 0)
+            return ret;
+
+        /* set the type to outer */
+        args->ech->type = 0;
+        /* set innerClientHelloLen to ClientHelloInner + padding + tag */
+        args->ech->paddingLen = 31 - ((args->length - 1) % 32);
+        args->ech->innerClientHelloLen = args->length +
+            args->ech->paddingLen + args->ech->hpke->Nt;
+        /* set the length back to before we computed ClientHelloInner size */
+        args->length = args->preXLength;
+    }
+#endif
+
     /* Include length of TLS extensions. */
     ret = TLSX_GetRequestSize(ssl, client_hello, &args->length);
     if (ret != 0)
@@ -4060,6 +4177,11 @@ int SendTls13ClientHello(WOLFSSL* ssl)
     }
     else
         XMEMCPY(args->output + args->idx, ssl->arrays->clientRandom, RAN_LEN);
+
+#if defined(HAVE_ECH)
+    args->clientRandomOffset = args->idx;
+#endif
+
     args->idx += RAN_LEN;
 
     if (ssl->session->sessionIDSz > 0) {
@@ -4121,14 +4243,78 @@ int SendTls13ClientHello(WOLFSSL* ssl)
     args->output[args->idx++] = COMP_LEN;
     args->output[args->idx++] = NO_COMPRESSION;
 
+#if defined(HAVE_ECH)
+    /* write inner then outer */
+    if (ssl->options.useEch == 1) {
+        /* set the type to inner */
+        args->ech->type = ECH_TYPE_INNER;
+
+        /* allocate the inner */
+        args->ech->innerClientHello =
+            (byte*)XMALLOC(args->ech->innerClientHelloLen - args->ech->hpke->Nt,
+            ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        if (args->ech->innerClientHello == NULL)
+            return MEMORY_E;
+
+        /* set the padding bytes to 0 */
+        XMEMSET(args->ech->innerClientHello + args->ech->innerClientHelloLen -
+            args->ech->hpke->Nt - args->ech->paddingLen, 0,
+            args->ech->paddingLen);
+
+        /* copy the client hello to the ech innerClientHello, exclude record */
+        /* and handshake headers */
+        XMEMCPY(args->ech->innerClientHello,
+            args->output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ,
+            args->idx - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ));
+
+        /* copy the client random to inner */
+        XMEMCPY(ssl->arrays->clientRandomInner, ssl->arrays->clientRandom,
+            RAN_LEN);
+
+        /* change the outer client random */
+        ret = wc_RNG_GenerateBlock(ssl->rng, args->output +
+            args->clientRandomOffset, RAN_LEN);
+        if (ret != 0)
+            return ret;
+
+        /* copy the new client random */
+        XMEMCPY(ssl->arrays->clientRandom, args->output +
+            args->clientRandomOffset, RAN_LEN);
+
+        /* write the extensions for inner */
+        args->length = 0;
+        ret = TLSX_WriteRequest(ssl, args->ech->innerClientHello + args->idx -
+            (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ), client_hello,
+            &args->length);
+        if (ret != 0)
+            return ret;
+
+        /* set the type to outer */
+        args->ech->type = 0;
+    }
+#endif
+
     /* Write out extensions for a request. */
     args->length = 0;
     ret = TLSX_WriteRequest(ssl, args->output + args->idx, client_hello,
         &args->length);
     if (ret != 0)
         return ret;
+
     args->idx += args->length;
 
+#if defined(HAVE_ECH)
+    /* encrypt and pack the ech innerClientHello */
+    if (ssl->options.useEch == 1) {
+        ret = TLSX_FinalizeEch(args->ech,
+            args->output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ,
+            args->sendSz - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ));
+
+        if (ret != 0)
+            return ret;
+    }
+#endif
+
 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
     /* Resumption has a specific set of extensions and binder is calculated
      * for each identity.
@@ -4138,7 +4324,7 @@ int SendTls13ClientHello(WOLFSSL* ssl)
     }
     else
 #endif
-        {
+    {
 #ifdef WOLFSSL_DTLS13
         if (ssl->options.dtls)
             ret = Dtls13HashHandshake(ssl,
@@ -4146,8 +4332,19 @@ int SendTls13ClientHello(WOLFSSL* ssl)
                 (word16)args->idx - Dtls13GetRlHeaderLength(ssl, 0));
         else
 #endif /* WOLFSSL_DTLS13 */
+        {
+#if defined(HAVE_ECH)
+            /* compute the inner hash */
+            if (ssl->options.useEch == 1) {
+                ret = EchHashHelloInner(ssl, args->ech);
+            }
+#endif
+
+            /* compute the outer hash */
+            if (ret == 0)
                 ret = HashOutput(ssl, args->output, args->idx, 0);
         }
+    }
     if (ret != 0)
         return ret;
 
@@ -4233,6 +4430,233 @@ static int Dtls13DoDowngrade(WOLFSSL* ssl)
 }
 #endif /* WOLFSSL_DTLS13 && !WOLFSSL_NO_CLIENT*/
 
+#if defined(HAVE_ECH)
+/* check if the server accepted ech or not */
+static int EchCheckAcceptance(WOLFSSL* ssl, const byte* input,
+    int serverRandomOffset, int helloSz)
+{
+    int ret = 0;
+    int digestType;
+    int digestSize;
+    HS_Hashes* tmpHashes;
+    HS_Hashes* acceptHashes;
+    byte zeros[WC_MAX_DIGEST_SIZE] = {0};
+    byte transcriptEchConf[WC_MAX_DIGEST_SIZE];
+    byte expandLabelPrk[WC_MAX_DIGEST_SIZE];
+    byte acceptConfirmation[ECH_ACCEPT_CONFIRMATION_SZ];
+
+    /* copy ech hashes to accept */
+    ret = InitHandshakeHashesAndCopy(ssl, ssl->hsHashesEch, &acceptHashes);
+
+    /* swap hsHashes to acceptHashes */
+    tmpHashes = ssl->hsHashes;
+    ssl->hsHashes = acceptHashes;
+
+    /* hash up to the last 8 bytes */
+    if (ret == 0)
+        ret = HashRaw(ssl, input, serverRandomOffset + RAN_LEN -
+            ECH_ACCEPT_CONFIRMATION_SZ);
+
+    /* hash 8 zeros */
+    if (ret == 0)
+        ret = HashRaw(ssl, zeros, ECH_ACCEPT_CONFIRMATION_SZ);
+
+    /* hash the rest of the hello */
+    if (ret == 0)
+        ret = HashRaw(ssl, input + serverRandomOffset + RAN_LEN,
+            helloSz + HANDSHAKE_HEADER_SZ - (serverRandomOffset + RAN_LEN));
+
+    /* get the modified transcript hash */
+    if (ret == 0)
+        ret = GetMsgHash(ssl, transcriptEchConf);
+
+    if (ret > 0)
+        ret = 0;
+
+    /* pick the right type and size based on mac_algorithm */
+    if (ret == 0)
+        switch (ssl->specs.mac_algorithm) {
+#ifndef NO_SHA256
+            case sha256_mac:
+                digestType = WC_SHA256;
+                digestSize = WC_SHA256_DIGEST_SIZE;
+                break;
+#endif /* !NO_SHA256 */
+#ifdef WOLFSSL_SHA384
+            case sha384_mac:
+                digestType = WC_SHA384;
+                digestSize = WC_SHA384_DIGEST_SIZE;
+                break;
+#endif /* WOLFSSL_SHA384 */
+#ifdef WOLFSSL_TLS13_SHA512
+            case sha512_mac:
+                digestType = WC_SHA512;
+                digestSize = WC_SHA512_DIGEST_SIZE;
+                break;
+#endif /* WOLFSSL_TLS13_SHA512 */
+            default:
+                ret = -1;
+                break;
+        }
+
+    /* extract clientRandomInner with a key of all zeros */
+    if (ret == 0)
+        ret = wc_HKDF_Extract(digestType, zeros, digestSize,
+            ssl->arrays->clientRandomInner, RAN_LEN, expandLabelPrk);
+
+    /* tls expand with the confirmation label */
+    if (ret == 0)
+        ret = wc_Tls13_HKDF_Expand_Label(acceptConfirmation,
+            ECH_ACCEPT_CONFIRMATION_SZ,
+            expandLabelPrk, digestSize, tls13ProtocolLabel,
+            TLS13_PROTOCOL_LABEL_SZ, echAcceptConfirmationLabel,
+            ECH_ACCEPT_CONFIRMATION_LABEL_SZ, transcriptEchConf, digestSize,
+            digestType);
+
+    if (ret == 0) {
+        /* last 8 bytes should match our expand output */
+        ret = XMEMCMP(acceptConfirmation,
+            ssl->arrays->serverRandom + RAN_LEN - ECH_ACCEPT_CONFIRMATION_SZ,
+            ECH_ACCEPT_CONFIRMATION_SZ);
+
+        /* ech accepted */
+        if (ret == 0) {
+            /* use the inner random for client random */
+            XMEMCPY(ssl->arrays->clientRandom, ssl->arrays->clientRandomInner,
+              RAN_LEN);
+
+            /* switch back to original hsHashes */
+            ssl->hsHashes = tmpHashes;
+
+            /* free hsHashes */
+            FreeHandshakeHashes(ssl);
+
+            /* set the final hsHashes to the ech hashes */
+            tmpHashes = ssl->hsHashesEch;
+
+            /* set hsHashesEch to NULL to avoid double free */
+            ssl->hsHashesEch = NULL;
+        }
+        /* ech rejected */
+        else {
+            /* switch to hsHashesEch */
+            ssl->hsHashes = ssl->hsHashesEch;
+
+            /* free ech hashes */
+            FreeHandshakeHashes(ssl);
+        }
+
+        /* continue with outer if we failed to verify ech was accepted */
+        ret = 0;
+    }
+
+    /* switch to acceptHashes */
+    ssl->hsHashes = acceptHashes;
+
+    /* free acceptHashes */
+    FreeHandshakeHashes(ssl);
+
+    ssl->hsHashes = tmpHashes;
+
+    return ret;
+}
+
+/* replace the last 8 bytes of the server random with the ech acceptance
+ * parameter, return status */
+static int EchWriteAcceptance(WOLFSSL* ssl, byte* output,
+  int serverRandomOffset, int helloSz)
+{
+    int ret = 0;
+    int digestType;
+    int digestSize;
+    HS_Hashes* tmpHashes;
+    HS_Hashes* acceptHashes;
+    byte zeros[WC_MAX_DIGEST_SIZE] = {0};
+    byte transcriptEchConf[WC_MAX_DIGEST_SIZE];
+    byte expandLabelPrk[WC_MAX_DIGEST_SIZE];
+
+    /* copy ech hashes to accept */
+    ret = InitHandshakeHashesAndCopy(ssl, ssl->hsHashes, &acceptHashes);
+
+    /* swap hsHashes to acceptHashes */
+    tmpHashes = ssl->hsHashes;
+    ssl->hsHashes = acceptHashes;
+
+    /* hash up to the last 8 bytes */
+    if (ret == 0)
+        ret = HashRaw(ssl, output, serverRandomOffset + RAN_LEN -
+            ECH_ACCEPT_CONFIRMATION_SZ);
+
+    /* hash 8 zeros */
+    if (ret == 0)
+        ret = HashRaw(ssl, zeros, ECH_ACCEPT_CONFIRMATION_SZ);
+
+    /* hash the rest of the hello */
+    if (ret == 0)
+        ret = HashRaw(ssl, output + serverRandomOffset + RAN_LEN,
+            helloSz - (serverRandomOffset + RAN_LEN));
+
+    /* get the modified transcript hash */
+    if (ret == 0)
+        ret = GetMsgHash(ssl, transcriptEchConf);
+
+    if (ret > 0)
+        ret = 0;
+
+    /* pick the right type and size based on mac_algorithm */
+    if (ret == 0)
+        switch (ssl->specs.mac_algorithm) {
+#ifndef NO_SHA256
+            case sha256_mac:
+                digestType = WC_SHA256;
+                digestSize = WC_SHA256_DIGEST_SIZE;
+                break;
+#endif /* !NO_SHA256 */
+#ifdef WOLFSSL_SHA384
+            case sha384_mac:
+                digestType = WC_SHA384;
+                digestSize = WC_SHA384_DIGEST_SIZE;
+                break;
+#endif /* WOLFSSL_SHA384 */
+#ifdef WOLFSSL_TLS13_SHA512
+            case sha512_mac:
+                digestType = WC_SHA512;
+                digestSize = WC_SHA512_DIGEST_SIZE;
+                break;
+#endif /* WOLFSSL_TLS13_SHA512 */
+            default:
+                ret = -1;
+                break;
+        }
+
+    /* extract clientRandom with a key of all zeros */
+    if (ret == 0)
+        ret = wc_HKDF_Extract(digestType, zeros, digestSize,
+            ssl->arrays->clientRandom, RAN_LEN, expandLabelPrk);
+
+    /* tls expand with the confirmation label */
+    if (ret == 0)
+        ret = wc_Tls13_HKDF_Expand_Label(
+            output + serverRandomOffset + RAN_LEN - ECH_ACCEPT_CONFIRMATION_SZ,
+            ECH_ACCEPT_CONFIRMATION_SZ,
+            expandLabelPrk, digestSize, tls13ProtocolLabel,
+            TLS13_PROTOCOL_LABEL_SZ, echAcceptConfirmationLabel,
+            ECH_ACCEPT_CONFIRMATION_LABEL_SZ, transcriptEchConf, digestSize,
+            digestType);
+
+    if (ret == 0)
+        XMEMCPY(ssl->arrays->serverRandom, output + serverRandomOffset,
+            RAN_LEN);
+
+    /* free acceptHashes */
+    FreeHandshakeHashes(ssl);
+
+    ssl->hsHashes = tmpHashes;
+
+    return ret;
+}
+#endif
+
 /* handle processing of TLS 1.3 server_hello (2) and hello_retry_request (6) */
 /* Handle the ServerHello message from the server.
  * Only a client will receive this message.
@@ -4253,6 +4677,9 @@ typedef struct Dsh13Args {
     word16          totalExtSz;
     byte            sessIdSz;
     byte            extMsgType;
+#if defined(HAVE_ECH)
+    int             serverRandomOffset;
+#endif
 } Dsh13Args;
 
 int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
@@ -4400,6 +4827,9 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
 
     /* Server random - keep for debugging. */
     XMEMCPY(ssl->arrays->serverRandom, input + args->idx, RAN_LEN);
+#if defined(HAVE_ECH)
+    args->serverRandomOffset = args->idx;
+#endif
     args->idx += RAN_LEN;
 
     /* Session id */
@@ -4682,6 +5112,16 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
     ret = SetCipherSpecs(ssl);
     if (ret != 0)
         return ret;
+
+#if defined(HAVE_ECH)
+    /* check for acceptConfirmation and HashInput with 8 0 bytes */
+    if (ssl->options.useEch == 1) {
+        ret = EchCheckAcceptance(ssl, input, args->serverRandomOffset, helloSz);
+        if (ret != 0)
+            return ret;
+    }
+#endif
+
 #ifdef HAVE_NULL_CIPHER
     if (ssl->options.cipherSuite0 == ECC_BYTE &&
                               (ssl->options.cipherSuite == TLS_SHA256_SHA256 ||
@@ -5814,6 +6254,10 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
 #else
     Dch13Args  args[1];
 #endif
+#if defined(HAVE_ECH)
+    word32 echInOutIdx;
+    TLSX* echX = NULL;
+#endif
 
     WOLFSSL_START(WC_FUNC_CLIENT_HELLO_DO);
     WOLFSSL_ENTER("DoTls13ClientHello");
@@ -6027,12 +6471,31 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
     if ((ret = TLSX_PopulateExtensions(ssl, 1)) != 0)
         goto exit_dch;
 
+#if defined(HAVE_ECH)
+    if (ssl->ctx->echConfigs != NULL) {
+        /* save the start of the buffer so we can use it when parsing ech */
+        echX = TLSX_Find(ssl->extensions, TLSX_ECH);
+
+        if (echX == NULL)
+            return -1;
+
+        ((WOLFSSL_ECH*)echX->data)->aad = input + HANDSHAKE_HEADER_SZ;
+        ((WOLFSSL_ECH*)echX->data)->aadLen = helloSz;
+    }
+#endif
+
     /* Parse extensions */
     if ((ret = TLSX_Parse(ssl, input + args->idx, totalExtSz, client_hello,
                                                             args->clSuites))) {
         goto exit_dch;
     }
 
+#if defined(HAVE_ECH)
+    /* jump to the end to clean things up */
+    if (echX != NULL && ((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE)
+        goto exit_dch;
+#endif
+
 #ifdef HAVE_SNI
         if ((ret = SNI_Callback(ssl)) != 0)
             goto exit_dch;
@@ -6267,6 +6730,27 @@ exit_dch:
         WOLFSSL_ERROR_VERBOSE(ret);
     }
 
+#if defined(HAVE_ECH)
+    /* do the hello again with the inner */
+    if (echX != NULL && ((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) {
+        /* reset the idx */
+        echInOutIdx = args->begin;
+
+        /* add the header to the inner hello */
+        AddTls13HandShakeHeader(((WOLFSSL_ECH*)echX->data)->innerClientHello,
+            ((WOLFSSL_ECH*)echX->data)->innerClientHelloLen, 0, 0,
+            client_hello, ssl);
+
+        ret = DoTls13ClientHello(ssl,
+            ((WOLFSSL_ECH*)echX->data)->innerClientHello,
+            &echInOutIdx, ((WOLFSSL_ECH*)echX->data)->innerClientHelloLen);
+
+        /* inner hello succeeded, consider this handshake message processed */
+        if (ret == 0)
+            *inOutIdx = args->begin + helloSz;
+    }
+#endif
+
     return ret;
 }
 
@@ -6284,6 +6768,10 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType)
     word16 length;
     word32 idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ;
     int    sendSz;
+#if defined(HAVE_ECH)
+    TLSX* echX = NULL;
+    word32 serverRandomOffset;
+#endif
 
     WOLFSSL_START(WC_FUNC_SERVER_HELLO_SEND);
     WOLFSSL_ENTER("SendTls13ServerHello");
@@ -6335,6 +6823,10 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType)
         XMEMCPY(output + idx, helloRetryRequestRandom, RAN_LEN);
     }
 
+#if defined(HAVE_ECH)
+    serverRandomOffset = idx;
+#endif
+
     /* Store in SSL for debugging. */
     XMEMCPY(ssl->arrays->serverRandom, output + idx, RAN_LEN);
     idx += RAN_LEN;
@@ -6386,7 +6878,26 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType)
         else
 #endif /* WOLFSSL_DTLS13 */
         {
-            ret = HashOutput(ssl, output, sendSz, 0);
+#if defined(HAVE_ECH)
+            if (ssl->ctx->echConfigs != NULL) {
+                echX = TLSX_Find(ssl->extensions, TLSX_ECH);
+
+                if (echX == NULL)
+                    return -1;
+
+                /* replace the last 8 bytes of server random with the accept */
+                if (((WOLFSSL_ECH*)echX->data)->state == ECH_PARSED_INTERNAL) {
+                    ret = EchWriteAcceptance(ssl, output + RECORD_HEADER_SZ,
+                        serverRandomOffset - RECORD_HEADER_SZ,
+                        sendSz - RECORD_HEADER_SZ);
+
+                    /* remove ech so we don't keep sending it in write */
+                    TLSX_Remove(&ssl->extensions, TLSX_ECH, ssl->heap);
+                }
+            }
+#endif
+            if (ret == 0)
+                ret = HashOutput(ssl, output, sendSz, 0);
         }
     }
 

+ 247 - 1
tests/api.c

@@ -340,7 +340,8 @@
 #if (defined(SESSION_CERTS) && defined(TEST_PEER_CERT_CHAIN)) || \
     defined(HAVE_SESSION_TICKET) || (defined(OPENSSL_EXTRA) && \
     defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_CERT_GEN)) || \
-    defined(WOLFSSL_TEST_STATIC_BUILD) || defined(WOLFSSL_DTLS)
+    defined(WOLFSSL_TEST_STATIC_BUILD) || defined(WOLFSSL_DTLS) || \
+    defined(HAVE_ECH)
     /* for testing SSL_get_peer_cert_chain, or SESSION_TICKET_HINT_DEFAULT,
      * for setting authKeyIdSrc in WOLFSSL_X509, or testing DTLS sequence
      * number tracking */
@@ -8542,51 +8543,61 @@ static int test_wolfSSL_UseSNI_connection(void)
     server_cb.devId = testDevId;
 
     /* success case at ctx */
+    printf("success case at ctx\n");
     client_cb.ctx_ready = use_SNI_at_ctx; client_cb.ssl_ready = NULL; client_cb.on_result = NULL;
     server_cb.ctx_ready = use_SNI_at_ctx; server_cb.ssl_ready = NULL; server_cb.on_result = verify_SNI_real_matching;
     test_wolfSSL_client_server(&client_cb, &server_cb);
 
     /* success case at ssl */
+    printf("success case at ssl\n");
     client_cb.ctx_ready = NULL; client_cb.ssl_ready = use_SNI_at_ssl; client_cb.on_result = verify_SNI_real_matching;
     server_cb.ctx_ready = NULL; server_cb.ssl_ready = use_SNI_at_ssl; server_cb.on_result = verify_SNI_real_matching;
     test_wolfSSL_client_server(&client_cb, &server_cb);
 
     /* default mismatch behavior */
+    printf("default mismatch behavior\n");
     client_cb.ctx_ready = NULL; client_cb.ssl_ready = different_SNI_at_ssl; client_cb.on_result = verify_FATAL_ERROR_on_client;
     server_cb.ctx_ready = NULL; server_cb.ssl_ready = use_SNI_at_ssl;       server_cb.on_result = verify_UNKNOWN_SNI_on_server;
     test_wolfSSL_client_server(&client_cb, &server_cb);
 
     /* continue on mismatch */
+    printf("continue on mismatch\n");
     client_cb.ctx_ready = NULL; client_cb.ssl_ready = different_SNI_at_ssl;         client_cb.on_result = NULL;
     server_cb.ctx_ready = NULL; server_cb.ssl_ready = use_SNI_WITH_CONTINUE_at_ssl; server_cb.on_result = verify_SNI_no_matching;
     test_wolfSSL_client_server(&client_cb, &server_cb);
 
     /* fake answer on mismatch */
+    printf("fake answer on mismatch\n");
     client_cb.ctx_ready = NULL; client_cb.ssl_ready = different_SNI_at_ssl;            client_cb.on_result = NULL;
     server_cb.ctx_ready = NULL; server_cb.ssl_ready = use_SNI_WITH_FAKE_ANSWER_at_ssl; server_cb.on_result = verify_SNI_fake_matching;
     test_wolfSSL_client_server(&client_cb, &server_cb);
 
     /* sni abort - success */
+    printf("sni abort - success\n");
     client_cb.ctx_ready = use_SNI_at_ctx;           client_cb.ssl_ready = NULL; client_cb.on_result = NULL;
     server_cb.ctx_ready = use_MANDATORY_SNI_at_ctx; server_cb.ssl_ready = NULL; server_cb.on_result = verify_SNI_real_matching;
     test_wolfSSL_client_server(&client_cb, &server_cb);
 
     /* sni abort - abort when absent (ctx) */
+    printf("sni abort - abort when absent (ctx)\n");
     client_cb.ctx_ready = NULL;                     client_cb.ssl_ready = NULL; client_cb.on_result = verify_FATAL_ERROR_on_client;
     server_cb.ctx_ready = use_MANDATORY_SNI_at_ctx; server_cb.ssl_ready = NULL; server_cb.on_result = verify_SNI_ABSENT_on_server;
     test_wolfSSL_client_server(&client_cb, &server_cb);
 
     /* sni abort - abort when absent (ssl) */
+    printf("sni abort - abort when absent (ssl)\n");
     client_cb.ctx_ready = NULL; client_cb.ssl_ready = NULL;                     client_cb.on_result = verify_FATAL_ERROR_on_client;
     server_cb.ctx_ready = NULL; server_cb.ssl_ready = use_MANDATORY_SNI_at_ssl; server_cb.on_result = verify_SNI_ABSENT_on_server;
     test_wolfSSL_client_server(&client_cb, &server_cb);
 
     /* sni abort - success when overwritten */
+    printf("sni abort - success when overwritten\n");
     client_cb.ctx_ready = NULL;                     client_cb.ssl_ready = NULL;           client_cb.on_result = NULL;
     server_cb.ctx_ready = use_MANDATORY_SNI_at_ctx; server_cb.ssl_ready = use_SNI_at_ssl; server_cb.on_result = verify_SNI_no_matching;
     test_wolfSSL_client_server(&client_cb, &server_cb);
 
     /* sni abort - success when allowing mismatches */
+    printf("sni abort - success when allowing mismatches\n");
     client_cb.ctx_ready = NULL;                            client_cb.ssl_ready = different_SNI_at_ssl; client_cb.on_result = NULL;
     server_cb.ctx_ready = use_PSEUDO_MANDATORY_SNI_at_ctx; server_cb.ssl_ready = NULL;                 server_cb.on_result = verify_SNI_fake_matching;
     test_wolfSSL_client_server(&client_cb, &server_cb);
@@ -35256,6 +35267,89 @@ static int test_wolfSSL_CTX_add_client_CA(void)
 #endif /* OPENSSL_EXTRA  && !NO_RSA && !NO_CERTS && !NO_WOLFSSL_CLIENT */
     return res;
 }
+#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
+static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args)
+{
+    callback_functions* callbacks = ((func_args*)args)->callbacks;
+    WOLFSSL_CTX* ctx       = callbacks->ctx;
+    WOLFSSL*  ssl   = NULL;
+    SOCKET_T  sfd   = 0;
+    SOCKET_T  cfd   = 0;
+    word16    port;
+    char      input[1024];
+    int       idx;
+    int       ret, err = 0;
+    const char* privateName = "ech-private-name.com";
+    int         privateNameLen = (int)XSTRLEN(privateName);
+
+    ((func_args*)args)->return_code = TEST_FAIL;
+    port = ((func_args*)args)->signal->port;
+
+    AssertIntEQ(WOLFSSL_SUCCESS,
+        wolfSSL_CTX_load_verify_locations(ctx, cliCertFile, 0));
+
+    AssertIntEQ(WOLFSSL_SUCCESS,
+        wolfSSL_CTX_use_certificate_file(ctx, svrCertFile,
+            WOLFSSL_FILETYPE_PEM));
+
+    AssertIntEQ(WOLFSSL_SUCCESS,
+        wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile,
+                 WOLFSSL_FILETYPE_PEM));
+
+    if (callbacks->ctx_ready)
+        callbacks->ctx_ready(ctx);
+
+    ssl = wolfSSL_new(ctx);
+
+    /* set the sni for the server */
+    wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, privateName, privateNameLen);
+
+    tcp_accept(&sfd, &cfd, (func_args*)args, port, 0, 0, 0, 0, 1, NULL, NULL);
+    CloseSocket(sfd);
+    AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_set_fd(ssl, cfd));
+
+    if (callbacks->ssl_ready)
+        callbacks->ssl_ready(ssl);
+
+    do {
+        err = 0; /* Reset error */
+        ret = wolfSSL_accept(ssl);
+        if (ret != WOLFSSL_SUCCESS) {
+            err = wolfSSL_get_error(ssl, 0);
+        }
+    } while (ret != WOLFSSL_SUCCESS && err == WC_PENDING_E);
+
+    if (ret != WOLFSSL_SUCCESS) {
+        char buff[WOLFSSL_MAX_ERROR_SZ];
+        printf("error = %d, %s\n", err, wolfSSL_ERR_error_string(err, buff));
+    }
+    else {
+        if (0 < (idx = wolfSSL_read(ssl, input, sizeof(input)-1))) {
+            input[idx] = 0;
+            printf("Client message: %s\n", input);
+        }
+
+        AssertIntEQ(privateNameLen, wolfSSL_write(ssl, privateName,
+            privateNameLen));
+        ((func_args*)args)->return_code = TEST_SUCCESS;
+    }
+
+    if (callbacks->on_result)
+        callbacks->on_result(ssl);
+
+    wolfSSL_shutdown(ssl);
+    wolfSSL_free(ssl);
+    wolfSSL_CTX_free(ctx);
+    CloseSocket(cfd);
+
+#ifdef FP_ECC
+    wc_ecc_fp_free();
+#endif
+
+    return 0;
+}
+#endif /* HAVE_ECH && WOLFSSL_TLS13 */
+
 #if defined(OPENSSL_EXTRA) && defined(HAVE_SECRET_CALLBACK)
 static THREAD_RETURN WOLFSSL_THREAD server_task(void* args)
 {
@@ -35621,6 +35715,154 @@ static int test_wolfSSL_Tls13_Key_Logging_test(void)
 #endif /* OPENSSL_EXTRA && HAVE_SECRET_CALLBACK && WOLFSSL_TLS13 */
     return res;
 }
+#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
+static int test_wolfSSL_Tls13_ECH_params(void)
+{
+#if !defined(NO_WOLFSSL_CLIENT)
+    word32 outputLen = 0;
+    byte testBuf[72];
+    WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
+    WOLFSSL     *ssl = wolfSSL_new(ctx);
+
+    AssertNotNull(ctx);
+    AssertNotNull(ssl);
+
+    /* invalid ctx */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(NULL,
+        "ech-public-name.com", 0, 0, 0));
+    /* invalid public name */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, NULL, 0,
+        0, 0));
+    /* invalid algorithms */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx,
+        "ech-public-name.com", 1000, 1000, 1000));
+
+    /* invalid ctx */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(NULL, NULL,
+        &outputLen));
+    /* invalid output len */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, NULL, NULL));
+
+    /* invalid ssl */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(NULL,
+        (char*)testBuf, sizeof(testBuf)));
+    /* invalid configs64 */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, NULL,
+        sizeof(testBuf)));
+    /* invalid size */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl,
+        (char*)testBuf, 0));
+
+    /* invalid ssl */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(NULL, testBuf,
+        sizeof(testBuf)));
+    /* invalid configs */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, NULL,
+        sizeof(testBuf)));
+    /* invalid size */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, testBuf, 0));
+
+    /* invalid ssl */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(NULL, NULL, &outputLen));
+    /* invalid size */
+    AssertIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(ssl, NULL, NULL));
+
+    wolfSSL_free(ssl);
+    wolfSSL_CTX_free(ctx);
+#endif /* !NO_WOLFSSL_CLIENT */
+
+    return TEST_SUCCESS;
+}
+
+static int test_wolfSSL_Tls13_ECH(void)
+{
+    tcp_ready ready;
+    func_args client_args;
+    func_args server_args;
+    THREAD_TYPE serverThread;
+    callback_functions server_cbf;
+    callback_functions client_cbf;
+    SOCKET_T sockfd = 0;
+    WOLFSSL_CTX* ctx;
+    WOLFSSL*     ssl;
+    const char* publicName = "ech-public-name.com";
+    const char* privateName = "ech-private-name.com";
+    int privateNameLen = 20;
+    char reply[1024];
+    int replyLen = 0;
+    byte rawEchConfig[128];
+    word32 rawEchConfigLen = sizeof(rawEchConfig);
+
+    InitTcpReady(&ready);
+    ready.port = 22222;
+
+    XMEMSET(&client_args, 0, sizeof(func_args));
+    XMEMSET(&server_args, 0, sizeof(func_args));
+    XMEMSET(&server_cbf, 0, sizeof(callback_functions));
+    XMEMSET(&client_cbf, 0, sizeof(callback_functions));
+    server_cbf.method     = wolfTLSv1_3_server_method;  /* TLS1.3 */
+
+    /* create the server context here so we can get the ech config */
+    AssertNotNull(server_cbf.ctx =
+        wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
+
+    /* generate ech config */
+    AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(server_cbf.ctx,
+        publicName, 0, 0, 0));
+
+    /* get the config for the client to use */
+    AssertIntEQ(WOLFSSL_SUCCESS,
+        wolfSSL_CTX_GetEchConfigs(server_cbf.ctx, rawEchConfig,
+        &rawEchConfigLen));
+
+    server_args.callbacks = &server_cbf;
+    server_args.signal    = &ready;
+
+    /* start server task */
+    start_thread(server_task_ech, &server_args, &serverThread);
+    wait_tcp_ready(&server_args);
+
+    /* run as a TLS1.3 client */
+    AssertNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()));
+    AssertIntEQ(WOLFSSL_SUCCESS,
+            wolfSSL_CTX_load_verify_locations(ctx, caCertFile, 0));
+    AssertIntEQ(WOLFSSL_SUCCESS,
+        wolfSSL_CTX_use_certificate_file(ctx, cliCertFile, SSL_FILETYPE_PEM));
+    AssertIntEQ(WOLFSSL_SUCCESS,
+        wolfSSL_CTX_use_PrivateKey_file(ctx, cliKeyFile, SSL_FILETYPE_PEM));
+
+    tcp_connect(&sockfd, wolfSSLIP, server_args.signal->port, 0, 0, NULL);
+
+    /* get connected the server task */
+    AssertNotNull(ssl = wolfSSL_new(ctx));
+
+    /* set the ech configs for the client */
+    AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, rawEchConfig,
+        rawEchConfigLen));
+
+    /* set the sni for the client */
+    AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME,
+        privateName, privateNameLen));
+
+    AssertIntEQ(wolfSSL_set_fd(ssl, sockfd), WOLFSSL_SUCCESS);
+    AssertIntEQ(wolfSSL_connect(ssl), WOLFSSL_SUCCESS);
+    AssertIntEQ(wolfSSL_write(ssl, privateName, privateNameLen),
+        privateNameLen);
+    AssertIntGT((replyLen = wolfSSL_read(ssl, reply, sizeof(reply))), 0);
+    /* add th null terminator for string compare */
+    reply[replyLen] = 0;
+    /* check that the server replied with the private name */
+    AssertStrEQ(privateName, reply);
+    wolfSSL_free(ssl);
+    wolfSSL_CTX_free(ctx);
+
+    join_thread(serverThread);
+
+    FreeTcpReady(&ready);
+
+    return TEST_SUCCESS;
+}
+#endif /* HAVE_ECH && WOLFSSL_TLS13 */
 
 #if defined(HAVE_IO_TESTS_DEPENDENCIES) && \
 defined(OPENSSL_EXTRA) && !defined(NO_CERTS) && \
@@ -60131,6 +60373,10 @@ TEST_CASE testCases[] = {
     TEST_DECL(test_wolfSSL_DisableExtendedMasterSecret),
     TEST_DECL(test_wolfSSL_wolfSSL_UseSecureRenegotiation),
     TEST_DECL(test_tls_ext_duplicate),
+#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
+    TEST_DECL(test_wolfSSL_Tls13_ECH_params),
+    TEST_DECL(test_wolfSSL_Tls13_ECH),
+#endif
 
     /* X509 tests */
     TEST_DECL(test_wolfSSL_X509_NAME_get_entry),

+ 1163 - 0
wolfcrypt/src/hpke.c

@@ -0,0 +1,1163 @@
+/* hpke.c
+ *
+ * Copyright (C) 2006-2022 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of 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.
+ *
+ * wolfSSL 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 a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+/* The HPKE supports ECC and X25519 with AES GCM only.
+ * TODO: Add X448 and ChaCha20
+ */
+
+#ifdef HAVE_CONFIG_H
+    #include <config.h>
+#endif
+
+#include <wolfssl/wolfcrypt/settings.h>
+
+#if defined(HAVE_HPKE) && (defined(HAVE_ECC) || defined(HAVE_CURVE25519)) && \
+    defined(HAVE_AESGCM)
+
+#include <wolfssl/wolfcrypt/error-crypt.h>
+#include <wolfssl/wolfcrypt/ecc.h>
+#include <wolfssl/wolfcrypt/curve25519.h>
+#include <wolfssl/wolfcrypt/curve448.h>
+#include <wolfssl/wolfcrypt/hmac.h>
+#include <wolfssl/wolfcrypt/hash.h>
+#include <wolfssl/wolfcrypt/sha256.h>
+#include <wolfssl/wolfcrypt/sha512.h>
+#include <wolfssl/wolfcrypt/aes.h>
+#include <wolfssl/wolfcrypt/hpke.h>
+
+const int hpkeSupportedKem[HPKE_SUPPORTED_KEM_LEN] = {
+    DHKEM_P256_HKDF_SHA256,
+    DHKEM_P384_HKDF_SHA384,
+    DHKEM_P521_HKDF_SHA512,
+    DHKEM_X25519_HKDF_SHA256,
+};
+
+const int hpkeSupportedKdf[HPKE_SUPPORTED_KDF_LEN] = {
+    HKDF_SHA256,
+    HKDF_SHA384,
+    HKDF_SHA512,
+};
+
+const int hpkeSupportedAead[HPKE_SUPPORTED_AEAD_LEN] = {
+    HPKE_AES_128_GCM,
+    HPKE_AES_256_GCM,
+};
+
+static const char* KEM_STR = "KEM";
+static const int   KEM_STR_LEN = 3;
+
+static const char* HPKE_STR = "HPKE";
+static const int   HPKE_STR_LEN = 4;
+
+static const char* HPKE_VERSION_STR = "HPKE-v1";
+static const int   HPKE_VERSION_STR_LEN = 7;
+
+static const char* EAE_PRK_LABEL_STR = "eae_prk";
+static const int   EAE_PRK_LABEL_STR_LEN = 7;
+
+static const char* SHARED_SECRET_LABEL_STR = "shared_secret";
+static const int   SHARED_SECRET_LABEL_STR_LEN = 13;
+
+static const char* PSK_ID_HASH_LABEL_STR = "psk_id_hash";
+static const int   PSK_ID_HASH_LABEL_STR_LEN = 11;
+
+static const char* INFO_HASH_LABEL_STR = "info_hash";
+static const int   INFO_HASH_LABEL_STR_LEN = 9;
+
+static const char* SECRET_LABEL_STR = "secret";
+static const int   SECRET_LABEL_STR_LEN = 6;
+
+static const char* KEY_LABEL_STR = "key";
+static const int   KEY_LABEL_STR_LEN = 3;
+
+static const char* BASE_NONCE_LABEL_STR = "base_nonce";
+static const int   BASE_NONCE_LABEL_STR_LEN = 10;
+
+static const char* EXP_LABEL_STR = "exp";
+static const int   EXP_LABEL_STR_LEN = 3;
+
+/* encode n as a byte string with length w, return 0 or error */
+static int I2OSP(int n, int w, byte* out)
+{
+    int i;
+
+    if (w <= 0 || w > 32) {
+        return MP_VAL;
+    }
+
+    /* if width is less than int max check that n is less than w bytes max */
+    /* if width is greater than int max check that n is less than int max */
+    if ((w < 4 && n > ((1 << (w * 8)) - 1)) || (w >= 4 && n > 0x7fffffff)) {
+        return MP_VAL;
+    }
+
+    /* make sure the byte string is cleared */
+    XMEMSET( out, 0, w );
+
+    for (i = 0; i < w && n > 0; i++) {
+        out[w-(i + 1)] = (byte)n;
+        n >>= 8;
+    }
+
+    return 0;
+}
+
+/* initialize the hpke struct with the desired ciphersuites, return 0 or error*/
+int wc_HpkeInit(Hpke* hpke, int kem, int kdf, int aead, void* heap)
+{
+    int ret;
+    byte* id;
+
+    if (hpke == NULL || kem == 0 || kdf == 0 || aead == 0) {
+        return BAD_FUNC_ARG;
+    }
+
+    XMEMSET(hpke, 0, sizeof(*hpke));
+    hpke->kem = kem;
+    hpke->kdf = kdf;
+    hpke->aead = aead;
+    hpke->heap = heap;
+
+    /* set kem_suite_id */
+    id = hpke->kem_suite_id;
+
+    XMEMCPY(id, KEM_STR, KEM_STR_LEN);
+    id += KEM_STR_LEN;
+
+    ret = I2OSP(kem, 2, id);
+
+    /* set hpke_suite_id */
+    id = hpke->hpke_suite_id;
+
+    XMEMCPY(id, HPKE_STR, HPKE_STR_LEN);
+    id += HPKE_STR_LEN;
+
+    if (ret == 0) {
+        ret = I2OSP(kem, 2, id);
+        id += 2;
+    }
+    if (ret == 0) {
+        ret = I2OSP(kdf, 2, id);
+        id += 2;
+    }
+    if (ret == 0) {
+        ret = I2OSP(aead, 2, id);
+    }
+
+    if (ret == 0) {
+        switch (kem) {
+#if defined(HAVE_ECC)
+#if defined(WOLFSSL_SHA224) || !defined(NO_SHA256)
+        case DHKEM_P256_HKDF_SHA256:
+            hpke->curve_id = ECC_SECP256R1;
+            hpke->Nsecret = WC_SHA256_DIGEST_SIZE;
+            hpke->Nh = WC_SHA256_DIGEST_SIZE;
+            hpke->Ndh = wc_ecc_get_curve_size_from_id(hpke->curve_id);
+            hpke->Npk = 1 + hpke->Ndh * 2;
+            break;
+#endif
+
+#ifdef WOLFSSL_SHA384
+        case DHKEM_P384_HKDF_SHA384:
+            hpke->curve_id = ECC_SECP384R1;
+            hpke->Nsecret = WC_SHA384_DIGEST_SIZE;
+            hpke->Nh = WC_SHA384_DIGEST_SIZE;
+            hpke->Ndh = wc_ecc_get_curve_size_from_id(hpke->curve_id);
+            hpke->Npk = 1 + hpke->Ndh * 2;
+            break;
+#endif
+
+#if defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512)
+        case DHKEM_P521_HKDF_SHA512:
+            hpke->curve_id = ECC_SECP521R1;
+            hpke->Nsecret = WC_SHA512_DIGEST_SIZE;
+            hpke->Nh = WC_SHA512_DIGEST_SIZE;
+            hpke->Ndh = wc_ecc_get_curve_size_from_id(hpke->curve_id);
+            hpke->Npk = 1 + hpke->Ndh * 2;
+            break;
+#endif
+#endif
+
+#if defined(HAVE_CURVE25519) &&\
+    (defined(WOLFSSL_SHA224) || !defined(NO_SHA256))
+        case DHKEM_X25519_HKDF_SHA256:
+            hpke->Nsecret = WC_SHA256_DIGEST_SIZE;
+            hpke->Nh = WC_SHA256_DIGEST_SIZE;
+            hpke->Ndh = CURVE25519_KEYSIZE;
+            hpke->Npk = CURVE25519_PUB_KEY_SIZE;
+            break;
+#endif
+
+#if defined(HAVE_CURVE448) &&\
+    (defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512))
+        case DHKEM_X448_HKDF_SHA512:
+            hpke->Nsecret = WC_SHA512_DIGEST_SIZE;
+            hpke->Nh = WC_SHA512_DIGEST_SIZE;
+            /* size of x448 shared secret */
+            hpke->Ndh = 64;
+            hpke->Npk = CURVE448_PUB_KEY_SIZE;
+            ret = BAD_FUNC_ARG; /* TODO: Add X448 */
+            break;
+#endif
+
+        default:
+            ret = BAD_FUNC_ARG;
+            break;
+        }
+    }
+
+    if (ret == 0) {
+        switch (kdf) {
+        case HKDF_SHA256:
+            hpke->kdf_digest = WC_SHA256;
+            break;
+
+        case HKDF_SHA384:
+            hpke->kdf_digest = WC_SHA384;
+            break;
+
+        case HKDF_SHA512:
+            hpke->kdf_digest = WC_SHA512;
+            break;
+
+        default:
+            ret = BAD_FUNC_ARG;
+            break;
+        }
+    }
+
+    if (ret == 0) {
+        switch (aead) {
+        case HPKE_AES_128_GCM:
+            hpke->Nk = AES_128_KEY_SIZE;
+            hpke->Nn = GCM_NONCE_MID_SZ;
+            hpke->Nt = AES_BLOCK_SIZE;
+            break;
+
+        case HPKE_AES_256_GCM:
+            hpke->Nk = AES_256_KEY_SIZE;
+            hpke->Nn = GCM_NONCE_MID_SZ;
+            hpke->Nt = AES_BLOCK_SIZE;
+            break;
+
+        default:
+            ret = BAD_FUNC_ARG;
+            break;
+        }
+    }
+
+    if ((int)hpke->Ndh < 0) {
+        return hpke->Ndh;
+    }
+
+    return ret;
+}
+
+/* generate a keypair for use with the supplied hpke kem method, return 0 or
+ * error */
+int wc_HpkeGenerateKeyPair(Hpke* hpke, void** keypair, WC_RNG* rng)
+{
+    int ret = 0;
+
+    if (hpke == NULL || keypair == NULL || rng == NULL)
+        return BAD_FUNC_ARG;
+
+    switch (hpke->kem) {
+#if defined(HAVE_ECC)
+        case DHKEM_P256_HKDF_SHA256:
+            *keypair = wc_ecc_key_new(hpke->heap);
+            if (*keypair != NULL)
+                ret = wc_ecc_make_key_ex(rng, 32, (ecc_key*)*keypair,
+                    ECC_SECP256R1);
+            break;
+        case DHKEM_P384_HKDF_SHA384:
+            *keypair = wc_ecc_key_new(hpke->heap);
+            if (*keypair != NULL)
+                ret = wc_ecc_make_key_ex(rng, 48, (ecc_key*)*keypair,
+                    ECC_SECP384R1);
+            break;
+        case DHKEM_P521_HKDF_SHA512:
+            *keypair = wc_ecc_key_new(hpke->heap);
+            if (*keypair != NULL)
+                ret = wc_ecc_make_key_ex(rng, 66, (ecc_key*)*keypair,
+                  ECC_SECP521R1);
+            break;
+#endif
+#if defined(HAVE_CURVE25519)
+        case DHKEM_X25519_HKDF_SHA256:
+            *keypair = XMALLOC(sizeof(curve25519_key), hpke->heap,
+                DYNAMIC_TYPE_CURVE25519);
+            if (*keypair != NULL) {
+                ret = wc_curve25519_init_ex((curve25519_key*)*keypair,
+                    hpke->heap, INVALID_DEVID);
+                if (ret == 0)
+                    ret = wc_curve25519_make_key(rng, 32,
+                        (curve25519_key*)*keypair);
+            }
+            break;
+#endif
+        case DHKEM_X448_HKDF_SHA512:
+            /* TODO: Add X448 */
+        default:
+            ret = BAD_FUNC_ARG;
+            break;
+    }
+
+    if (ret == 0 && *keypair == NULL)
+        ret = MEMORY_E;
+
+    if (ret != 0 && *keypair != NULL) {
+        wc_HpkeFreeKey(hpke, hpke->kem, *keypair, hpke->heap);
+        *keypair = NULL;
+    }
+
+    return ret;
+}
+
+/* encode the provided kem key into a byte string, return 0 or error */
+int wc_HpkeSerializePublicKey(Hpke* hpke, void* key, byte* out, word16* outSz)
+{
+    int ret;
+    word32 tmpOutSz;
+
+    if (hpke == NULL || key == NULL || out == NULL || outSz == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+    tmpOutSz = *outSz;
+
+    switch (hpke->kem)
+    {
+#if defined(HAVE_ECC)
+        case DHKEM_P256_HKDF_SHA256:
+        case DHKEM_P384_HKDF_SHA384:
+        case DHKEM_P521_HKDF_SHA512:
+            /* export x963 uncompressed */
+            ret = wc_ecc_export_x963_ex((ecc_key*)key, out, &tmpOutSz, 0);
+            break;
+#endif
+#if defined(HAVE_CURVE25519)
+        case DHKEM_X25519_HKDF_SHA256:
+            ret = wc_curve25519_export_public_ex((curve25519_key*)key, out,
+                &tmpOutSz, EC25519_LITTLE_ENDIAN);
+            break;
+#endif
+        case DHKEM_X448_HKDF_SHA512:
+        default:
+            ret = -1;
+            break;
+    }
+
+    *outSz = tmpOutSz;
+
+    return ret;
+}
+
+/* load a serialized kem key into a wolfcrypt key struct depending on the kem */
+int wc_HpkeDeserializePublicKey(Hpke* hpke, void** key, const byte* in,
+    word16 inSz)
+{
+    int ret = 0;
+
+    if (hpke == NULL || key == NULL || in == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+    if (inSz < (word32)hpke->Npk) {
+        return BUFFER_E;
+    }
+
+    switch (hpke->kem)
+    {
+#if defined(HAVE_ECC)
+        case DHKEM_P256_HKDF_SHA256:
+        case DHKEM_P384_HKDF_SHA384:
+        case DHKEM_P521_HKDF_SHA512:
+            /* init the ecc key */
+            *key = wc_ecc_key_new(hpke->heap);
+            if (*key != NULL) {
+                /* import the x963 key */
+                ret = wc_ecc_import_x963_ex(in, inSz, (ecc_key*)*key,
+                    hpke->curve_id);
+            }
+            break;
+#endif
+#if defined(HAVE_CURVE25519)
+        case DHKEM_X25519_HKDF_SHA256:
+            *key = XMALLOC(sizeof(curve25519_key), hpke->heap,
+                DYNAMIC_TYPE_CURVE25519);
+            if (*key != NULL) {
+                ret = wc_curve25519_init_ex((curve25519_key*)*key, hpke->heap,
+                    INVALID_DEVID);
+                if (ret == 0)
+                    ret = wc_curve25519_import_public_ex(in, inSz,
+                        (curve25519_key*)*key, EC25519_LITTLE_ENDIAN);
+            }
+            break;
+#endif
+        case DHKEM_X448_HKDF_SHA512:
+        default:
+            ret = -1;
+            break;
+    }
+
+    if (ret == 0 && *key == NULL)
+        ret = MEMORY_E;
+
+    if (ret != 0 && *key != NULL) {
+        wc_HpkeFreeKey(hpke, hpke->kem, *key, hpke->heap);
+        *key = NULL;
+    }
+
+    return ret;
+}
+
+/* free a kem key */
+void wc_HpkeFreeKey(Hpke* hpke, word16 kem, void* keypair, void* heap)
+{
+    switch (kem)
+    {
+#if defined(HAVE_ECC)
+        case DHKEM_P256_HKDF_SHA256:
+        case DHKEM_P384_HKDF_SHA384:
+        case DHKEM_P521_HKDF_SHA512:
+            wc_ecc_key_free((ecc_key*)keypair);
+            break;
+#endif
+#if defined(HAVE_CURVE25519)
+        case DHKEM_X25519_HKDF_SHA256:
+            wc_curve25519_free((curve25519_key*)keypair);
+            XFREE(keypair, heap, DYNAMIC_TYPE_CURVE25519);
+            break;
+#endif
+        case DHKEM_X448_HKDF_SHA512:
+            /* TODO: Add X448 */
+        default:
+            break;
+    }
+    (void)hpke;
+    (void)heap;
+}
+
+static int wc_HpkeLabeledExtract(Hpke* hpke, byte* suite_id,
+    word32 suite_id_len, byte* salt, word32 salt_len, byte* label,
+    word32 label_len, byte* ikm, word32 ikm_len, byte* out)
+{
+    int ret;
+    byte* labeled_ikm_p;
+#ifndef WOLFSSL_SMALL_STACK
+    byte labeled_ikm[MAX_HPKE_LABEL_SZ];
+#else
+    byte* labeled_ikm;
+#endif
+
+    if (hpke == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    labeled_ikm = (byte*)XMALLOC(MAX_HPKE_LABEL_SZ, hpke->heap,
+        DYNAMIC_TYPE_TMP_BUFFER);
+    if (labeled_ikm == NULL) {
+        return MEMORY_E;
+    }
+#endif
+
+    /* concat the labeled_ikm */
+    /* version */
+    XMEMCPY(labeled_ikm, HPKE_VERSION_STR, HPKE_VERSION_STR_LEN);
+    labeled_ikm_p = labeled_ikm + HPKE_VERSION_STR_LEN;
+
+    /* suite_id */
+    XMEMCPY(labeled_ikm_p, suite_id, suite_id_len);
+    labeled_ikm_p += suite_id_len;
+
+    /* label */
+    XMEMCPY(labeled_ikm_p, label, label_len);
+    labeled_ikm_p += label_len;
+
+    /* ikm */
+    if (ikm_len != 0) {
+        XMEMCPY(labeled_ikm_p, ikm, ikm_len);
+        labeled_ikm_p += ikm_len;
+    }
+
+    /* call extract */
+    ret = wc_HKDF_Extract(hpke->kdf_digest, salt, salt_len, labeled_ikm,
+        (word32)(size_t)(labeled_ikm_p - labeled_ikm), out);
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(labeled_ikm, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+#endif
+
+    return ret;
+}
+
+/* do hkdf expand with the format specified in the hpke rfc, return 0 or
+ * error */
+static int wc_HpkeLabeledExpand(Hpke* hpke, byte* suite_id, word32 suite_id_len,
+    byte* prk, word32 prk_len, byte* label, word32 label_len, byte* info,
+    word32 infoSz, word32 L, byte* out)
+{
+    int ret;
+    byte* labeled_info_p;
+#ifndef WOLFSSL_SMALL_STACK
+    byte labeled_info[MAX_HPKE_LABEL_SZ];
+#else
+    byte* labeled_info;
+#endif
+
+    if (hpke == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    labeled_info = (byte*)XMALLOC(MAX_HPKE_LABEL_SZ, hpke->heap,
+        DYNAMIC_TYPE_TMP_BUFFER);
+    if (labeled_info == NULL) {
+        return MEMORY_E;
+    }
+#endif
+
+    /* copy length */
+    ret = I2OSP(L, 2, labeled_info);
+    labeled_info_p = labeled_info + 2;
+
+    if (ret == 0) {
+        /* version */
+        XMEMCPY(labeled_info_p, HPKE_VERSION_STR, HPKE_VERSION_STR_LEN);
+        labeled_info_p += HPKE_VERSION_STR_LEN;
+
+        /* suite_id */
+        XMEMCPY(labeled_info_p, suite_id, suite_id_len);
+        labeled_info_p += suite_id_len;
+
+        /* label */
+        XMEMCPY(labeled_info_p, label, label_len);
+        labeled_info_p += label_len;
+
+        /* info */
+        XMEMCPY(labeled_info_p, info, infoSz);
+        labeled_info_p += infoSz;
+
+        /* call expand */
+        ret = wc_HKDF_Expand(hpke->kdf_digest,
+            prk, prk_len,
+            labeled_info, (word32)(size_t)(labeled_info_p - labeled_info),
+            out, L);
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(labeled_info, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+#endif
+
+    return ret;
+}
+
+/* compute the current nonce from the base nonce using the sequence value,
+ * return 0 or error */
+static int wc_HpkeContextComputeNonce(Hpke* hpke, HpkeBaseContext* context,
+    byte* out)
+{
+    int i;
+    int ret;
+    byte seq_bytes[HPKE_Nn_MAX];
+
+    /* convert the sequence into a byte string with the same length as the
+     * nonce */
+    ret = I2OSP(context->seq, hpke->Nn, seq_bytes);
+    if (ret == 0) {
+        for (i = 0; i < (int)hpke->Nn; i++) {
+            out[i] = (context->base_nonce[i] ^ seq_bytes[i]);
+        }
+    }
+
+    return ret;
+}
+
+/* call extract and expand as specified in the hpke rfc, return 0 or error */
+static int wc_HpkeExtractAndExpand( Hpke* hpke, byte* dh, word32 dh_len,
+    byte* kemContext, word32 kem_context_length, byte* sharedSecret)
+{
+    int ret;
+    /* max length is the largest hmac digest possible */
+#ifndef WOLFSSL_SMALL_STACK
+    byte eae_prk[WC_MAX_DIGEST_SIZE];
+#else
+    byte* eae_prk;
+#endif
+
+    if (hpke == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    eae_prk = (byte*)XMALLOC(WC_MAX_DIGEST_SIZE, hpke->heap,
+        DYNAMIC_TYPE_DIGEST);
+    if (eae_prk == NULL) {
+        return MEMORY_E;
+    }
+#endif
+
+    /* extract */
+    ret = wc_HpkeLabeledExtract(hpke, hpke->kem_suite_id,
+        sizeof( hpke->kem_suite_id ), NULL, 0, (byte*)EAE_PRK_LABEL_STR,
+        EAE_PRK_LABEL_STR_LEN, dh, dh_len, eae_prk);
+
+    /* expand */
+    if ( ret == 0 )
+        ret = wc_HpkeLabeledExpand(hpke, hpke->kem_suite_id,
+            sizeof( hpke->kem_suite_id ), eae_prk, hpke->Nh,
+            (byte*)SHARED_SECRET_LABEL_STR, SHARED_SECRET_LABEL_STR_LEN,
+            kemContext, kem_context_length, hpke->Nsecret, sharedSecret);
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(eae_prk, hpke->heap, DYNAMIC_TYPE_DIGEST);
+#endif
+
+    return ret;
+}
+
+/* derive the key, nonce and exporter secret and store them in the context
+ * struct, return 0 or error */
+static int wc_HpkeKeyScheduleBase(Hpke* hpke, HpkeBaseContext* context,
+    byte* sharedSecret, byte* info, word32 infoSz)
+{
+    int ret;
+#ifndef WOLFSSL_SMALL_STACK
+    /* 1 for mode and WC_MAX_DIGEST_SIZE times 2 for psk_id_hash and */
+    /* info_hash */
+    byte key_schedule_context[1 + 2 * WC_MAX_DIGEST_SIZE];
+    /* maximum size of secret is largest hash of extract */
+    byte secret[WC_MAX_DIGEST_SIZE];
+#else
+    byte* key_schedule_context = NULL;
+    byte* secret = NULL;
+#endif
+
+    if (hpke == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    key_schedule_context = (byte*)XMALLOC((1 + 2 * WC_MAX_DIGEST_SIZE),
+        hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+    secret = (byte*)XMALLOC(WC_MAX_DIGEST_SIZE, hpke->heap,
+        DYNAMIC_TYPE_DIGEST);
+    if (key_schedule_context == NULL || secret == NULL) {
+        XFREE(key_schedule_context, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        XFREE(secret, hpke->heap, DYNAMIC_TYPE_DIGEST);
+        return MEMORY_E;
+    }
+#endif
+
+    /* set the sequence to 0 */
+    context->seq = 0;
+
+    /* 0 for mode */
+    key_schedule_context[0] = 0;
+
+    /* extract psk_id, which for base is null */
+    ret = wc_HpkeLabeledExtract(hpke, hpke->hpke_suite_id,
+        sizeof( hpke->hpke_suite_id ), NULL, 0, (byte*)PSK_ID_HASH_LABEL_STR,
+        PSK_ID_HASH_LABEL_STR_LEN, NULL, 0, key_schedule_context + 1);
+
+    /* extract info */
+    if (ret == 0) {
+        ret = wc_HpkeLabeledExtract(hpke, hpke->hpke_suite_id,
+            sizeof( hpke->hpke_suite_id ), NULL, 0, (byte*)INFO_HASH_LABEL_STR,
+            INFO_HASH_LABEL_STR_LEN, info, infoSz,
+            key_schedule_context + 1 + hpke->Nh);
+    }
+
+    /* extract secret */
+    if (ret == 0) {
+        ret = wc_HpkeLabeledExtract(hpke, hpke->hpke_suite_id,
+            sizeof( hpke->hpke_suite_id ), sharedSecret, hpke->Nsecret,
+            (byte*)SECRET_LABEL_STR, SECRET_LABEL_STR_LEN, NULL, 0, secret);
+    }
+
+    /* expand key */
+    if (ret == 0)
+        ret = wc_HpkeLabeledExpand(hpke, hpke->hpke_suite_id,
+            sizeof( hpke->hpke_suite_id ), secret, hpke->Nh,
+            (byte*)KEY_LABEL_STR, KEY_LABEL_STR_LEN, key_schedule_context,
+            1 + 2 * hpke->Nh, hpke->Nk, context->key);
+
+    /* expand nonce */
+    if (ret == 0) {
+        ret = wc_HpkeLabeledExpand(hpke, hpke->hpke_suite_id,
+            sizeof( hpke->hpke_suite_id ), secret, hpke->Nh,
+            (byte*)BASE_NONCE_LABEL_STR, BASE_NONCE_LABEL_STR_LEN,
+            key_schedule_context, 1 + 2 * hpke->Nh, hpke->Nn,
+            context->base_nonce);
+    }
+
+    /* expand exporter_secret */
+    if (ret == 0) {
+        ret = wc_HpkeLabeledExpand(hpke, hpke->hpke_suite_id,
+            sizeof( hpke->hpke_suite_id ), secret, hpke->Nh,
+            (byte*)EXP_LABEL_STR, EXP_LABEL_STR_LEN, key_schedule_context,
+            1 + 2 * hpke->Nh, hpke->Nh, context->exporter_secret);
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(key_schedule_context, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+    XFREE(secret, hpke->heap, DYNAMIC_TYPE_DIGEST);
+#endif
+
+    return ret;
+}
+
+/* compute the shared secret from the ephemeral and receiver kem keys */
+static int wc_HpkeEncap(Hpke* hpke, void* ephemeralKey, void* receiverKey,
+    byte* sharedSecret)
+{
+    int ret;
+    word32 dh_len;
+    word16 receiverPubKeySz = hpke->Npk;
+    word16 ephemeralPubKeySz = hpke->Npk;
+#ifndef WOLFSSL_SMALL_STACK
+    byte dh[HPKE_Ndh_MAX];
+    byte kemContext[HPKE_Npk_MAX * 2];
+#else
+    byte* dh = NULL;
+    byte* kemContext = NULL;
+#endif
+
+    if (hpke == NULL || ephemeralKey == NULL || receiverKey == NULL ||
+        sharedSecret == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    dh = (byte*)XMALLOC(hpke->Ndh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+    kemContext = (byte*)XMALLOC(hpke->Npk * 2, hpke->heap,
+        DYNAMIC_TYPE_TMP_BUFFER);
+    if (dh == NULL || kemContext == NULL) {
+        XFREE(dh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        XFREE(kemContext, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        return MEMORY_E;
+    }
+#endif
+
+    /* generate dh */
+    dh_len = hpke->Ndh;
+
+    switch (hpke->kem)
+    {
+#if defined(HAVE_ECC)
+        case DHKEM_P256_HKDF_SHA256:
+        case DHKEM_P384_HKDF_SHA384:
+        case DHKEM_P521_HKDF_SHA512:
+            ((ecc_key*)ephemeralKey)->rng = wc_rng_new(NULL, 0, hpke->heap);
+
+            ret = wc_ecc_shared_secret((ecc_key*)ephemeralKey,
+                (ecc_key*)receiverKey, dh, &dh_len);
+
+            wc_rng_free(((ecc_key*)ephemeralKey)->rng);
+            break;
+#endif
+#if defined(HAVE_CURVE25519)
+        case DHKEM_X25519_HKDF_SHA256:
+            ret = wc_curve25519_shared_secret_ex((curve25519_key*)ephemeralKey,
+                (curve25519_key*)receiverKey, dh, &dh_len,
+                EC25519_LITTLE_ENDIAN);
+            break;
+#endif
+        case DHKEM_X448_HKDF_SHA512:
+            /* TODO: Add X448 */
+        default:
+            ret = -1;
+            break;
+    }
+
+    if (ret == 0) {
+        /* serialize ephemeralKey into kemContext */
+        ret = wc_HpkeSerializePublicKey(hpke, ephemeralKey,
+            kemContext, &ephemeralPubKeySz);
+    }
+    if (ret == 0) {
+        /* serialize pkR into kemContext */
+        ret = wc_HpkeSerializePublicKey(hpke, receiverKey,
+            kemContext + ephemeralPubKeySz, &receiverPubKeySz);
+    }
+    if (ret == 0) {
+        /* compute the shared secret */
+        ret = wc_HpkeExtractAndExpand(hpke, dh, dh_len, kemContext,
+            hpke->Npk * 2, sharedSecret);
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(dh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+    XFREE(kemContext, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+#endif
+
+    return ret;
+}
+
+/* setup the sender context with shared key, nonce and exporter secret */
+static int wc_HpkeSetupBaseSender(Hpke* hpke, HpkeBaseContext* context,
+    void* ephemeralKey, void* receiverKey, byte* info, word32 infoSz)
+{
+    int ret;
+#ifndef WOLFSSL_SMALL_STACK
+    byte sharedSecret[HPKE_Nsecret_MAX];
+#else
+    byte* sharedSecret;
+#endif
+
+    if (hpke == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    sharedSecret = (byte*)XMALLOC(hpke->Nsecret, hpke->heap,
+        DYNAMIC_TYPE_TMP_BUFFER);
+#endif
+
+    /* encap */
+    ret = wc_HpkeEncap(hpke, ephemeralKey, receiverKey, sharedSecret);
+
+    /* schedule */
+    if (ret == 0) {
+        ret = wc_HpkeKeyScheduleBase(hpke, context, sharedSecret, info,
+            infoSz);
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(sharedSecret, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+#endif
+
+    return ret;
+}
+
+/* encrypt a message using an hpke base context, return 0 or error */
+static int wc_HpkeContextSealBase(Hpke* hpke, HpkeBaseContext* context,
+    byte* aad, word32 aadSz, byte* plaintext, word32 ptSz, byte* out)
+{
+    int ret;
+    byte nonce[HPKE_Nn_MAX];
+#ifndef WOLFSSL_SMALL_STACK
+    Aes aes_key[1];
+#else
+    Aes* aes_key;
+#endif
+
+    if (hpke == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    aes_key = (Aes*)XMALLOC(sizeof(Aes), hpke->heap, DYNAMIC_TYPE_AES);
+    if (aes_key == NULL) {
+        return MEMORY_E;
+    }
+#endif
+
+    ret = wc_AesInit(aes_key, hpke->heap, INVALID_DEVID);
+    if (ret == 0) {
+        ret = wc_HpkeContextComputeNonce(hpke, context, nonce);
+        if (ret == 0) {
+            ret = wc_AesGcmSetKey(aes_key, context->key, hpke->Nk);
+        }
+        if (ret == 0) {
+            ret = wc_AesGcmEncrypt(aes_key, out, plaintext, ptSz, nonce,
+                hpke->Nn, out + ptSz, hpke->Nt, aad, aadSz);
+        }
+        if (ret == 0) {
+            context->seq++;
+        }
+        wc_AesFree(aes_key);
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(aes_key, hpke->heap, DYNAMIC_TYPE_AES);
+#endif
+
+    return ret;
+}
+
+/* encrypt a message using the provided ephemeral and receiver kem keys */
+int wc_HpkeSealBase(Hpke* hpke, void* ephemeralKey, void* receiverKey,
+    byte* info, word32 infoSz, byte* aad, word32 aadSz, byte* plaintext,
+    word32 ptSz, byte* ciphertext)
+{
+    int ret;
+#ifdef WOLFSSL_SMALL_STACK
+    HpkeBaseContext* context;
+#else
+    HpkeBaseContext context[1];
+#endif
+
+    /* check that all the buffers are non NULL or optional with 0 length */
+    if (hpke == NULL || ephemeralKey == NULL || receiverKey == NULL ||
+        (info == NULL && infoSz != 0) || (aad == NULL && aadSz != 0) ||
+        plaintext == NULL || ciphertext == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    context = (HpkeBaseContext*)XMALLOC(sizeof(HpkeBaseContext), hpke->heap,
+        DYNAMIC_TYPE_TMP_BUFFER);
+    if (context == NULL) {
+        return MEMORY_E;
+    }
+#endif
+
+    /* setup the context and pubKey */
+    ret = wc_HpkeSetupBaseSender(hpke, context, ephemeralKey, receiverKey, info,
+        infoSz);
+
+    /* run seal using the context */
+    if (ret == 0)
+        ret = wc_HpkeContextSealBase(hpke, context, aad, aadSz, plaintext,
+            ptSz, ciphertext);
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(context, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+#endif
+
+    return ret;
+}
+
+/* compute the shared secret from the ephemeral and receiver kem keys */
+static int wc_HpkeDecap(Hpke* hpke, void* receiverKey, const byte* pubKey,
+    word16 pubKeySz, byte* sharedSecret)
+{
+    int ret;
+    word32 dh_len;
+    word16 receiverPubKeySz = hpke->Npk;
+    void* ephemeralKey = NULL;
+#ifndef WOLFSSL_SMALL_STACK
+    byte dh[HPKE_Ndh_MAX];
+    byte kemContext[HPKE_Npk_MAX * 2];
+#else
+    byte* dh = NULL;
+    byte* kemContext = NULL;
+#endif
+
+    if (hpke == NULL || receiverKey == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    dh = (byte*)XMALLOC(hpke->Ndh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+    kemContext = (byte*)XMALLOC(hpke->Npk * 2, hpke->heap,
+        DYNAMIC_TYPE_TMP_BUFFER);
+    if (dh == NULL || kemContext == NULL) {
+        XFREE(dh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        XFREE(kemContext, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+        return MEMORY_E;
+    }
+#endif
+
+    /* deserialize ephemeralKey from pubKey */
+    ret = wc_HpkeDeserializePublicKey(hpke, &ephemeralKey, pubKey, pubKeySz);
+
+    /* generate dh */
+    dh_len = hpke->Ndh;
+
+    if (ret == 0)
+        switch (hpke->kem)
+        {
+#if defined(HAVE_ECC)
+            case DHKEM_P256_HKDF_SHA256:
+            case DHKEM_P384_HKDF_SHA384:
+            case DHKEM_P521_HKDF_SHA512:
+                ((ecc_key*)receiverKey)->rng = wc_rng_new(NULL, 0, hpke->heap);
+
+                ret = wc_ecc_shared_secret((ecc_key*)receiverKey,
+                    (ecc_key*)ephemeralKey, dh, &dh_len);
+
+                wc_rng_free(((ecc_key*)receiverKey)->rng);
+                break;
+#endif
+#if defined(HAVE_CURVE25519)
+            case DHKEM_X25519_HKDF_SHA256:
+                ret = wc_curve25519_shared_secret_ex(
+                    (curve25519_key*)receiverKey, (curve25519_key*)ephemeralKey,
+                    dh, &dh_len, EC25519_LITTLE_ENDIAN);
+                break;
+#endif
+            case DHKEM_X448_HKDF_SHA512:
+                /* TODO: Add X448 */
+            default:
+                ret = -1;
+                break;
+        }
+
+    if (ephemeralKey != NULL)
+        wc_HpkeFreeKey(hpke, hpke->kem, ephemeralKey, hpke->heap);
+
+    if (ret == 0) {
+        /* copy pubKey into kemContext */
+        XMEMCPY(kemContext, pubKey, hpke->Npk);
+
+        /* serialize pkR into kemContext */
+        ret = wc_HpkeSerializePublicKey(hpke, receiverKey,
+            kemContext + hpke->Npk, &receiverPubKeySz);
+    }
+
+    /* compute the shared secret */
+    if (ret == 0) {
+        ret = wc_HpkeExtractAndExpand(hpke, dh, dh_len, kemContext,
+            hpke->Npk * 2, sharedSecret);
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(dh, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+    XFREE(kemContext, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+#endif
+
+    return ret;
+}
+
+/* setup an hpke base context for decrypting messages, return 0 or error */
+static int wc_HpkeSetupBaseReceiver(Hpke* hpke, HpkeBaseContext* context,
+    void* receiverKey, const byte* pubKey, word16 pubKeySz, byte* info,
+    word32 infoSz)
+{
+    int ret;
+#ifndef WOLFSSL_SMALL_STACK
+    byte sharedSecret[HPKE_Nsecret_MAX];
+#else
+    byte* sharedSecret;
+#endif
+
+#ifdef WOLFSSL_SMALL_STACK
+    sharedSecret = (byte*)XMALLOC(hpke->Nsecret, hpke->heap,
+        DYNAMIC_TYPE_TMP_BUFFER);
+    if (sharedSecret == NULL) {
+        return MEMORY_E;
+    }
+#endif
+
+    /* decap */
+    ret = wc_HpkeDecap(hpke, receiverKey, pubKey, pubKeySz, sharedSecret);
+
+    /* schedule */
+    if (ret == 0) {
+        ret = wc_HpkeKeyScheduleBase(hpke, context, sharedSecret, info,
+            infoSz);
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(sharedSecret, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+#endif
+
+    return ret;
+}
+
+/* decrypt a message using a setup hpke context, return 0 or error */
+static int wc_HpkeContextOpenBase(Hpke* hpke, HpkeBaseContext* context,
+    byte* aad, word32 aadSz, byte* ciphertext, word32 ctSz, byte* out)
+{
+    int ret;
+    byte nonce[HPKE_Nn_MAX];
+#ifndef WOLFSSL_SMALL_STACK
+    Aes aes_key[1];
+#else
+    Aes* aes_key;
+#endif
+
+    if (hpke == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    aes_key = (Aes*)XMALLOC(sizeof(Aes), hpke->heap, DYNAMIC_TYPE_AES);
+    if (aes_key == NULL) {
+        return MEMORY_E;
+    }
+#endif
+
+    ret = wc_HpkeContextComputeNonce(hpke, context, nonce);
+    if (ret == 0)
+        ret = wc_AesInit(aes_key, hpke->heap, INVALID_DEVID);
+    if (ret == 0) {
+        if (ret == 0) {
+            ret = wc_AesGcmSetKey(aes_key, context->key, hpke->Nk);
+        }
+        if (ret == 0) {
+            ret = wc_AesGcmDecrypt(aes_key, out, ciphertext, ctSz, nonce,
+                hpke->Nn, ciphertext + ctSz, hpke->Nt, aad, aadSz);
+        }
+        if (ret == 0) {
+            context->seq++;
+        }
+        wc_AesFree(aes_key);
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(aes_key, hpke->heap, DYNAMIC_TYPE_AES);
+#endif
+
+    return ret;
+}
+
+/* decrypt a message using the receiver and encoded ephemeral key, return 0 or
+ * error */
+int wc_HpkeOpenBase(Hpke* hpke, void* receiverKey, const byte* pubKey,
+    word16 pubKeySz, byte* info, word32 infoSz, byte* aad, word32 aadSz,
+    byte* ciphertext, word32 ctSz, byte* plaintext)
+{
+    int ret;
+#ifndef WOLFSSL_SMALL_STACK
+    HpkeBaseContext context[1];
+#else
+    HpkeBaseContext* context;
+#endif
+
+    /* check that all the buffer are non NULL or optional with 0 length */
+    if (hpke == NULL || receiverKey == NULL || pubKey == NULL ||
+        pubKeySz == 0 || (info == NULL && infoSz != 0) ||
+        (aad == NULL && aadSz != 0) || plaintext == NULL ||
+        ciphertext == NULL) {
+        return BAD_FUNC_ARG;
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    context = (HpkeBaseContext*)XMALLOC(sizeof(HpkeBaseContext), hpke->heap,
+        DYNAMIC_TYPE_TMP_BUFFER);
+    if (context == NULL) {
+        return MEMORY_E;
+    }
+#endif
+
+    /* setup receiver */
+    ret = wc_HpkeSetupBaseReceiver(hpke, context, receiverKey, pubKey,
+        pubKeySz, info, infoSz);
+
+    if (ret == 0) {
+        /* open the ciphertext */
+        ret = wc_HpkeContextOpenBase(hpke, context, aad, aadSz, ciphertext,
+            ctSz, plaintext);
+    }
+
+#ifdef WOLFSSL_SMALL_STACK
+    XFREE(context, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER);
+#endif
+
+    return ret;
+}
+
+#endif /* HAVE_HPKE && (HAVE_ECC || HAVE_CURVE25519) && HAVE_AESGCM */

+ 163 - 20
wolfcrypt/test/test.c

@@ -253,6 +253,9 @@
 #ifdef HAVE_ECC
     #include <wolfssl/wolfcrypt/ecc.h>
 #endif
+#ifdef HAVE_HPKE
+    #include <wolfssl/wolfcrypt/hpke.h>
+#endif
 #ifdef HAVE_CURVE25519
     #include <wolfssl/wolfcrypt/curve25519.h>
 #endif
@@ -434,6 +437,7 @@ WOLFSSL_TEST_SUBROUTINE int  sshkdf_test(void);
 WOLFSSL_TEST_SUBROUTINE int  tls13_kdf_test(void);
 #endif
 WOLFSSL_TEST_SUBROUTINE int  x963kdf_test(void);
+WOLFSSL_TEST_SUBROUTINE int  hpke_test(void);
 WOLFSSL_TEST_SUBROUTINE int  arc4_test(void);
 #ifdef WC_RC2
 WOLFSSL_TEST_SUBROUTINE int  rc2_test(void);
@@ -1064,6 +1068,13 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\
         TEST_PASS("X963-KDF    test passed!\n");
 #endif
 
+#if defined(HAVE_HPKE) && defined(HAVE_ECC) && defined(HAVE_AESGCM)
+    if ( (ret = hpke_test()) != 0)
+        return err_sys("HPKE     test failed!\n", ret);
+    else
+        TEST_PASS("HPKE     test passed!\n");
+#endif
+
 #if defined(HAVE_AESGCM) && defined(WOLFSSL_AES_128) && \
    !defined(WOLFSSL_AFALG_XILINX_AES) && !defined(WOLFSSL_XILINX_CRYPT)
     if ( (ret = gmac_test()) != 0)
@@ -22244,8 +22255,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void)
 
         ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz,
                 secret, hashAlgSz,
-                (byte*)protocolLabel, (word32)strlen(protocolLabel),
-                (byte*)ceTrafficLabel, (word32)strlen(ceTrafficLabel),
+                (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel),
+                (byte*)ceTrafficLabel, (word32)XSTRLEN(ceTrafficLabel),
                 tv->hashHello1, hashAlgSz, tv->hashAlg);
         if (ret != 0) break;
 
@@ -22254,8 +22265,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void)
 
         ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz,
                 secret, hashAlgSz,
-                (byte*)protocolLabel, (word32)strlen(protocolLabel),
-                (byte*)eExpMasterLabel, (word32)strlen(eExpMasterLabel),
+                (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel),
+                (byte*)eExpMasterLabel, (word32)XSTRLEN(eExpMasterLabel),
                 tv->hashHello1, hashAlgSz, tv->hashAlg);
         if (ret != 0) break;
 
@@ -22264,8 +22275,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void)
 
         ret = wc_Tls13_HKDF_Expand_Label(salt, hashAlgSz,
                 secret, hashAlgSz,
-                (byte*)protocolLabel, (word32)strlen(protocolLabel),
-                (byte*)derivedLabel, (word32)strlen(derivedLabel),
+                (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel),
+                (byte*)derivedLabel, (word32)XSTRLEN(derivedLabel),
                 hashZero, hashAlgSz, tv->hashAlg);
         if (ret != 0) break;
 
@@ -22276,8 +22287,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void)
 
         ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz,
                 secret, hashAlgSz,
-                (byte*)protocolLabel, (word32)strlen(protocolLabel),
-                (byte*)cHsTrafficLabel, (word32)strlen(cHsTrafficLabel),
+                (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel),
+                (byte*)cHsTrafficLabel, (word32)XSTRLEN(cHsTrafficLabel),
                 tv->hashHello2, hashAlgSz, tv->hashAlg);
         if (ret != 0) break;
 
@@ -22287,8 +22298,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void)
 
         ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz,
                 secret, hashAlgSz,
-                (byte*)protocolLabel, (word32)strlen(protocolLabel),
-                (byte*)sHsTrafficLabel, (word32)strlen(sHsTrafficLabel),
+                (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel),
+                (byte*)sHsTrafficLabel, (word32)XSTRLEN(sHsTrafficLabel),
                 tv->hashHello2, hashAlgSz, tv->hashAlg);
         if (ret != 0) break;
 
@@ -22297,8 +22308,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void)
 
         ret = wc_Tls13_HKDF_Expand_Label(salt, hashAlgSz,
                 secret, hashAlgSz,
-                (byte*)protocolLabel, (word32)strlen(protocolLabel),
-                (byte*)derivedLabel, (word32)strlen(derivedLabel),
+                (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel),
+                (byte*)derivedLabel, (word32)XSTRLEN(derivedLabel),
                 hashZero, hashAlgSz, tv->hashAlg);
         if (ret != 0) break;
 
@@ -22308,8 +22319,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void)
 
         ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz,
                 secret, hashAlgSz,
-                (byte*)protocolLabel, (word32)strlen(protocolLabel),
-                (byte*)cAppTrafficLabel, (word32)strlen(cAppTrafficLabel),
+                (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel),
+                (byte*)cAppTrafficLabel, (word32)XSTRLEN(cAppTrafficLabel),
                 tv->hashFinished1, hashAlgSz, tv->hashAlg);
         if (ret != 0) break;
 
@@ -22318,8 +22329,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void)
 
         ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz,
                 secret, hashAlgSz,
-                (byte*)protocolLabel, (word32)strlen(protocolLabel),
-                (byte*)sAppTrafficLabel, (word32)strlen(sAppTrafficLabel),
+                (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel),
+                (byte*)sAppTrafficLabel, (word32)XSTRLEN(sAppTrafficLabel),
                 tv->hashFinished1, hashAlgSz, tv->hashAlg);
         if (ret != 0) break;
 
@@ -22328,8 +22339,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void)
 
         ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz,
                 secret, hashAlgSz,
-                (byte*)protocolLabel, (word32)strlen(protocolLabel),
-                (byte*)expMasterLabel, (word32)strlen(expMasterLabel),
+                (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel),
+                (byte*)expMasterLabel, (word32)XSTRLEN(expMasterLabel),
                 tv->hashFinished1, hashAlgSz, tv->hashAlg);
         if (ret != 0) break;
 
@@ -22338,8 +22349,8 @@ WOLFSSL_TEST_SUBROUTINE int tls13_kdf_test(void)
 
         ret = wc_Tls13_HKDF_Expand_Label(output, hashAlgSz,
                 secret, hashAlgSz,
-                (byte*)protocolLabel, (word32)strlen(protocolLabel),
-                (byte*)resMasterLabel, (word32)strlen(resMasterLabel),
+                (byte*)protocolLabel, (word32)XSTRLEN(protocolLabel),
+                (byte*)resMasterLabel, (word32)XSTRLEN(resMasterLabel),
                 tv->hashFinished2, hashAlgSz, tv->hashAlg);
         if (ret != 0) break;
 
@@ -22498,6 +22509,138 @@ WOLFSSL_TEST_SUBROUTINE int x963kdf_test(void)
 
 #endif /* HAVE_X963_KDF */
 
+#if defined(HAVE_HPKE) && (defined(HAVE_ECC) || defined(HAVE_CURVE25519)) && \
+    defined(HAVE_AESGCM)
+
+static int hpke_test_single(Hpke* hpke)
+{
+    int ret = 0;
+    int rngRet = 0;
+    WC_RNG rng[1];
+    const char* start_text = "this is a test";
+    const char* info_text = "info";
+    const char* aad_text = "aad";
+    byte ciphertext[MAX_HPKE_LABEL_SZ];
+    byte plaintext[MAX_HPKE_LABEL_SZ];
+    void* receiverKey = NULL;
+    void* ephemeralKey = NULL;
+    uint8_t pubKey[HPKE_Npk_MAX]; /* public key */
+    word16 pubKeySz = (word16)sizeof(pubKey);
+
+    rngRet = ret = wc_InitRng(rng);
+
+    if (ret != 0)
+        return ret;
+
+    /* generate the keys */
+    if (ret == 0)
+        ret = wc_HpkeGenerateKeyPair(hpke, &ephemeralKey, rng);
+
+    if (ret == 0)
+        ret = wc_HpkeGenerateKeyPair(hpke, &receiverKey, rng);
+
+    /* seal */
+    if (ret == 0)
+        ret = wc_HpkeSealBase(hpke, ephemeralKey, receiverKey,
+            (byte*)info_text, (word32)XSTRLEN(info_text),
+            (byte*)aad_text, (word32)XSTRLEN(aad_text),
+            (byte*)start_text, (word32)XSTRLEN(start_text),
+            ciphertext);
+
+    /* export ephemeral key */
+    if (ret == 0)
+        ret = wc_HpkeSerializePublicKey(hpke, ephemeralKey, pubKey, &pubKeySz);
+
+    /* open with exported ephemeral key */
+    if (ret == 0)
+        ret = wc_HpkeOpenBase(hpke, receiverKey, pubKey, pubKeySz,
+            (byte*)info_text, (word32)XSTRLEN(info_text),
+            (byte*)aad_text, (word32)XSTRLEN(aad_text),
+            ciphertext, (word32)XSTRLEN(start_text),
+            plaintext);
+
+    if (ret == 0)
+        ret = XMEMCMP(plaintext, start_text, XSTRLEN(start_text));
+
+    if (ephemeralKey != NULL)
+        wc_HpkeFreeKey(hpke, hpke->kem, ephemeralKey, hpke->heap);
+
+    if (receiverKey != NULL)
+        wc_HpkeFreeKey(hpke, hpke->kem, receiverKey, hpke->heap);
+
+    if (rngRet == 0)
+        wc_FreeRng(rng);
+
+    return ret;
+}
+
+WOLFSSL_TEST_SUBROUTINE int hpke_test(void)
+{
+    int ret = 0;
+    Hpke hpke[1];
+
+#if defined(HAVE_ECC)
+    #if defined(WOLFSSL_SHA224) || !defined(NO_SHA256)
+    /* p256 */
+    ret = wc_HpkeInit(hpke, DHKEM_P256_HKDF_SHA256, HKDF_SHA256,
+        HPKE_AES_128_GCM, NULL);
+
+    if (ret != 0)
+        return ret;
+
+    ret = hpke_test_single(hpke);
+
+    if (ret != 0)
+        return ret;
+    #endif
+
+    #ifdef WOLFSSL_SHA384
+    /* p384 */
+    ret = wc_HpkeInit(hpke, DHKEM_P384_HKDF_SHA384, HKDF_SHA384,
+        HPKE_AES_128_GCM, NULL);
+
+    if (ret != 0)
+        return ret;
+
+    ret = hpke_test_single(hpke);
+
+    if (ret != 0)
+        return ret;
+    #endif
+
+    #if defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512)
+    /* p521 */
+    ret = wc_HpkeInit(hpke, DHKEM_P521_HKDF_SHA512, HKDF_SHA512,
+        HPKE_AES_128_GCM, NULL);
+
+    if (ret != 0)
+        return ret;
+
+    ret = hpke_test_single(hpke);
+
+    if (ret != 0)
+        return ret;
+    #endif
+#endif
+
+#if defined(HAVE_CURVE25519)
+    /* test with curve25519 and aes256 */
+    ret = wc_HpkeInit(hpke, DHKEM_X25519_HKDF_SHA256, HKDF_SHA256,
+        HPKE_AES_256_GCM, NULL);
+
+    if (ret != 0)
+        return ret;
+
+    ret = hpke_test_single(hpke);
+
+    if (ret != 0)
+        return ret;
+#endif
+
+    return ret;
+/* x448 and chacha20 are unimplemented */
+}
+#endif /* HAVE_HPKE && HAVE_ECC && HAVE_AESGCM */
 
 #ifdef HAVE_ECC
 

+ 88 - 3
wolfssl/internal.h

@@ -265,6 +265,8 @@
     #include <wolfssl/wolfcrypt/port/Renesas/renesas-tsip-crypt.h>
 #endif
 
+#include <wolfssl/wolfcrypt/hpke.h>
+
 #ifdef __cplusplus
     extern "C" {
 #endif
@@ -2592,8 +2594,74 @@ typedef enum {
 #ifdef WOLFSSL_QUIC
     TLSX_KEY_QUIC_TP_PARAMS_DRAFT   = 0xffa5, /* from draft-ietf-quic-tls-27 */
 #endif
+#if defined(HAVE_ECH)
+    TLSX_ECH                        = 0xfe0d, /* from draft-ietf-tls-esni-13 */
+#endif
 } TLSX_Type;
 
+#if defined(HAVE_ECH)
+
+typedef enum {
+    ECH_TYPE_OUTER = 0,
+    ECH_TYPE_INNER = 1
+} EchType;
+
+typedef enum {
+    ECH_WRITE_GREASE,
+    ECH_WRITE_REAL,
+    ECH_WRITE_RETRY_CONFIGS,
+    ECH_WRITE_NONE,
+    ECH_PARSED_INTERNAL,
+} EchState;
+
+typedef struct EchCipherSuite {
+    word16 kdfId;
+    word16 aeadId;
+} EchCipherSuite;
+
+typedef struct WOLFSSL_EchConfig {
+    byte* raw;
+    char* publicName;
+    void* receiverPrivkey;
+    struct WOLFSSL_EchConfig* next;
+    EchCipherSuite* cipherSuites;
+    word32 rawLen;
+    word16 kemId;
+    byte configId;
+    byte numCipherSuites;
+    byte receiverPubkey[HPKE_Npk_MAX];
+} WOLFSSL_EchConfig;
+
+typedef struct WOLFSSL_ECH {
+    Hpke* hpke;
+    const byte* aad;
+    void* ephemeralKey;
+    WOLFSSL_EchConfig* echConfig;
+    byte* innerClientHello;
+    byte* outerClientPayload;
+    EchCipherSuite cipherSuite;
+    word16 aadLen;
+    word16 paddingLen;
+    word16 innerClientHelloLen;
+    word16 kemId;
+    word16 encLen;
+    EchState state;
+    byte type;
+    byte configId;
+    byte enc[HPKE_Npk_MAX];
+} WOLFSSL_ECH;
+
+WOLFSSL_LOCAL int EchConfigGetSupportedCipherSuite(WOLFSSL_EchConfig* config);
+
+WOLFSSL_LOCAL int TLSX_FinalizeEch(WOLFSSL_ECH* ech, byte* aad, word32 aadLen);
+
+WOLFSSL_LOCAL int GetEchConfig(WOLFSSL_EchConfig* config, byte* output,
+    word32* outputLen);
+
+WOLFSSL_LOCAL int GetEchConfigsEx(WOLFSSL_EchConfig* configs,
+    byte* output, word32* outputLen);
+#endif
+
 typedef struct TLSX {
     TLSX_Type    type; /* Extension Type  */
     void*        data; /* Extension Data  */
@@ -3477,6 +3545,9 @@ struct WOLFSSL_CTX {
         const WOLFSSL_QUIC_METHOD *method;
     } quic;
 #endif
+#if defined(HAVE_ECH)
+    WOLFSSL_EchConfig* echConfigs;
+#endif
 };
 
 WOLFSSL_LOCAL
@@ -4240,6 +4311,12 @@ typedef struct Options {
 #ifdef WOLFSSL_TLS13
     word16            tls13MiddleBoxCompat:1; /* TLSv1.3 middlebox compatibility */
 #endif
+#ifdef WOLFSSL_DTLS_CID
+    byte              useDtlsCID:1;
+#endif /* WOLFSSL_DTLS_CID */
+#if defined(HAVE_ECH)
+    byte              useEch:1;
+#endif
 
     /* need full byte values for this section */
     byte            processReply;           /* nonblocking resume */
@@ -4283,9 +4360,6 @@ typedef struct Options {
 #ifdef WOLFSSL_TLS13
     byte            oldMinor;          /* client preferred version < TLS 1.3 */
 #endif
-#ifdef WOLFSSL_DTLS_CID
-    byte            useDtlsCID:1;
-#endif /* WOLFSSL_DTLS_CID */
 } Options;
 
 typedef struct Arrays {
@@ -4301,6 +4375,9 @@ typedef struct Arrays {
     byte            psk_key[MAX_PSK_KEY_LEN];
 #endif
     byte            clientRandom[RAN_LEN];
+#if defined(HAVE_ECH)
+    byte            clientRandomInner[RAN_LEN];
+#endif
     byte            serverRandom[RAN_LEN];
     byte            sessionID[ID_LEN];
     byte            sessionIDSz;
@@ -4855,6 +4932,9 @@ struct WOLFSSL {
     byte            serverSecret[SECRET_LEN];
 #endif
     HS_Hashes*      hsHashes;
+#if defined(HAVE_ECH)
+    HS_Hashes*      hsHashesEch;
+#endif
     void*           IOCB_ReadCtx;
     void*           IOCB_WriteCtx;
     WC_RNG*         rng;
@@ -5312,6 +5392,9 @@ struct WOLFSSL {
                                           * content have not been handled yet by quic */
     } quic;
 #endif /* WOLFSSL_QUIC */
+#if defined(HAVE_ECH)
+    WOLFSSL_EchConfig* echConfigs;
+#endif
 };
 
 /*
@@ -5817,6 +5900,8 @@ WOLFSSL_LOCAL int SetDhExternal(WOLFSSL_DH *dh);
 
 WOLFSSL_LOCAL int InitHandshakeHashes(WOLFSSL* ssl);
 WOLFSSL_LOCAL void FreeHandshakeHashes(WOLFSSL* ssl);
+WOLFSSL_LOCAL int InitHandshakeHashesAndCopy(WOLFSSL* ssl, HS_Hashes* source,
+    HS_Hashes** destination);
 
 
 #ifndef WOLFSSL_NO_TLS12

+ 19 - 1
wolfssl/ssl.h

@@ -992,6 +992,23 @@ WOLFSSL_API WOLFSSL_METHOD *wolfSSLv23_method(void);
 
 #endif /* WOLFSSL_DTLS */
 
+#if defined(HAVE_ECH)
+WOLFSSL_API int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx,
+    const char* publicName, word16 kemId, word16 kdfId, word16 aeadId);
+
+WOLFSSL_API int wolfSSL_CTX_GetEchConfigs(WOLFSSL_CTX* ctx, byte* output,
+    word32* outputLen);
+
+WOLFSSL_API int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64,
+    word32 echConfigs64Len);
+
+WOLFSSL_API int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs,
+    word32 echConfigsLen);
+
+WOLFSSL_API int wolfSSL_GetEchConfigs(WOLFSSL* ssl, byte* echConfigs,
+    word32* echConfigsLen);
+#endif /* HAVE_ECH */
+
 #ifdef HAVE_POLY1305
     WOLFSSL_API int wolfSSL_use_old_poly(WOLFSSL* ssl, int value);
 #endif
@@ -3702,7 +3719,8 @@ WOLFSSL_API void* wolfSSL_CTX_GetHeap(WOLFSSL_CTX* ctx, WOLFSSL* ssl);
 
 /* SNI types */
 enum {
-    WOLFSSL_SNI_HOST_NAME = 0
+    WOLFSSL_SNI_HOST_NAME = 0,
+    WOLFSSL_SNI_HOST_NAME_OUTER = 0,
 };
 
 WOLFSSL_ABI WOLFSSL_API int wolfSSL_UseSNI(WOLFSSL* ssl, unsigned char type,

+ 136 - 0
wolfssl/wolfcrypt/hpke.h

@@ -0,0 +1,136 @@
+/* hpke.h
+ *
+ * Copyright (C) 2006-2022 wolfSSL Inc.
+ *
+ * This file is part of wolfSSL.
+ *
+ * wolfSSL is free software; you can redistribute it and/or modify
+ * it under the terms of 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.
+ *
+ * wolfSSL 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 a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
+ */
+
+/*!
+    \file wolfssl/wolfcrypt/hpke.h
+*/
+
+#include <wolfssl/wolfcrypt/settings.h>
+#include <wolfssl/wolfcrypt/types.h>
+#include <wolfssl/wolfcrypt/ecc.h>
+
+#ifdef __cplusplus
+    extern "C" {
+#endif
+
+#if defined(HAVE_HPKE) && defined(HAVE_ECC)
+
+#ifndef WOLFCRYPT_HPKE
+#define WOLFCRYPT_HPKE
+
+/* KEM enum */
+enum {
+    DHKEM_P256_HKDF_SHA256 = 0x0010,
+    DHKEM_P384_HKDF_SHA384 = 0x0011,
+    DHKEM_P521_HKDF_SHA512 = 0x0012,
+    DHKEM_X25519_HKDF_SHA256 = 0x0020,
+    DHKEM_X448_HKDF_SHA512 = 0x0021,
+};
+
+#define DHKEM_P256_ENC_LEN 65
+#define DHKEM_P384_ENC_LEN 97
+#define DHKEM_P521_ENC_LEN 133
+#define DHKEM_X25519_ENC_LEN 32
+#define DHKEM_X448_ENC_LEN 56
+
+/* KDF enum */
+enum {
+    HKDF_SHA256 = 0x0001,
+    HKDF_SHA384 = 0x0002,
+    HKDF_SHA512 = 0x0003,
+};
+
+/* AEAD enum */
+enum {
+    HPKE_AES_128_GCM = 0x0001,
+    HPKE_AES_256_GCM = 0x0002,
+};
+
+/* TODO better way of doing this */
+#define HPKE_SUPPORTED_KEM_LEN 4
+#define HPKE_SUPPORTED_KDF_LEN 3
+#define HPKE_SUPPORTED_AEAD_LEN 2
+extern const int hpkeSupportedKem[HPKE_SUPPORTED_KEM_LEN];
+extern const int hpkeSupportedKdf[HPKE_SUPPORTED_KDF_LEN];
+extern const int hpkeSupportedAead[HPKE_SUPPORTED_AEAD_LEN];
+
+#define HPKE_Nh_MAX 64
+#define HPKE_Nk_MAX 32
+#define HPKE_Nn_MAX 12
+#define HPKE_Nt_MAX 16
+#define HPKE_Ndh_MAX 66
+#define HPKE_Npk_MAX 133
+#define HPKE_Nsecret_MAX 64
+#define KEM_SUITE_ID_LEN 5
+#define HPKE_SUITE_ID_LEN 10
+
+#ifndef MAX_HPKE_LABEL_SZ
+#define MAX_HPKE_LABEL_SZ 512
+#endif
+
+typedef struct {
+    void* heap;
+    word32 kem;
+    word32 kdf;
+    word32 aead;
+    word32 Nh;
+    word32 Nk;
+    word32 Nn;
+    word32 Nt;
+    word32 Ndh;
+    word32 Npk;
+    word32 Nsecret;
+    int kdf_digest;
+    int curve_id;
+    byte kem_suite_id[KEM_SUITE_ID_LEN];
+    byte hpke_suite_id[HPKE_SUITE_ID_LEN];
+} Hpke;
+
+typedef struct {
+    int seq;
+    byte key[HPKE_Nk_MAX];
+    byte base_nonce[HPKE_Nn_MAX];
+    byte exporter_secret[HPKE_Nsecret_MAX];
+} HpkeBaseContext;
+
+
+WOLFSSL_API int wc_HpkeInit(Hpke* hpke, int kem, int kdf, int aead, void* heap);
+WOLFSSL_API int wc_HpkeGenerateKeyPair(Hpke* hpke, void** keypair, WC_RNG* rng);
+WOLFSSL_API int wc_HpkeSerializePublicKey(Hpke* hpke, void* key, byte* out,
+    word16* outSz);
+WOLFSSL_API int wc_HpkeDeserializePublicKey(Hpke* hpke, void** key,
+    const byte* in, word16 inSz);
+WOLFSSL_API void wc_HpkeFreeKey(Hpke* hpke, word16 kem, void* keypair,
+    void* heap);
+WOLFSSL_API int wc_HpkeSealBase(Hpke* hpke, void* ephemeralKey,
+    void* receiverKey, byte* info, word32 infoSz, byte* aad, word32 aadSz,
+    byte* plaintext, word32 ptSz, byte* ciphertext);
+WOLFSSL_API int wc_HpkeOpenBase(Hpke* hpke, void* receiverKey,
+    const byte* pubKey, word16 pubKeySz, byte* info, word32 infoSz, byte* aad,
+    word32 aadSz, byte* ciphertext, word32 ctSz, byte* plaintext);
+
+#endif
+
+#endif /* HAVE_HPKE && HAVE_ECC */
+
+#ifdef __cplusplus
+    }    /* extern "C" */
+#endif

+ 1 - 0
wolfssl/wolfcrypt/include.am

@@ -32,6 +32,7 @@ nobase_include_HEADERS+= \
                          wolfssl/wolfcrypt/fips_test.h \
                          wolfssl/wolfcrypt/hash.h \
                          wolfssl/wolfcrypt/hmac.h \
+                         wolfssl/wolfcrypt/hpke.h \
                          wolfssl/wolfcrypt/kdf.h \
                          wolfssl/wolfcrypt/integer.h \
                          wolfssl/wolfcrypt/md2.h \

+ 4 - 0
wolfssl/wolfcrypt/settings.h

@@ -2879,6 +2879,10 @@ extern void uITRON4_free(void *p) ;
     #define WOLFSSL_NO_SHAKE256
 #endif
 
+/* Encrypted Client Hello - requires HPKE */
+#if defined(HAVE_ECH) && !defined(HAVE_HPKE)
+    #define HAVE_HPKE
+#endif
 
 
 

Some files were not shown because too many files changed in this diff